Compare commits

...

33 Commits

Author SHA1 Message Date
Alberto Ponces
ed9038ffe8 build: Bump version to v0.0.14 2022-09-19 01:47:51 +01:00
Alberto Ponces
269a71d336 fix: Improve foreground service init and disable 2022-09-19 01:46:12 +01:00
Alberto Ponces
9405334a7d fix: Fix multiple APK shares 2022-09-19 01:45:24 +01:00
Alberto Ponces
2f14746124 feat: Add install error dialogs to prevent faulty installations 2022-09-19 01:44:27 +01:00
Alberto Ponces
6c2ceed91f fix: Improve API URL handling and force init after setting a new config 2022-09-19 00:28:26 +01:00
Alberto Ponces
c333fa3c33 fix: Prefer Yes/No instead of OK/Cancel on a few dialogs 2022-09-18 22:53:04 +01:00
Alberto Ponces
79d0396630 fix: No need for an uninstall confirmation dialog as OS already handles this 2022-09-18 22:47:24 +01:00
Unknown
110c3326bd fix: navigation bar color (#197) 2022-09-18 22:20:08 +01:00
Alberto Ponces
9c5b0b9c14 fix: Readd permission_handler with a proper fix 2022-09-18 22:13:29 +01:00
Aunali321
c5ad337daa feat: Custom api endpoints. (#216)
Co-authored-by: Alberto Ponces <ponces26@gmail.com>
2022-09-18 23:12:30 +05:30
Aunali321
a9f64e449b refactor: basic prerequisites in readme. 2022-09-18 23:01:12 +05:30
Unknown
48f0bc625d feat: animate switching updates on HomeView (#209) 2022-09-18 16:49:18 +01:00
Aunali321
4c6b93320f build: Bump bump version to v0.0.13 2022-09-18 12:22:39 +05:30
Aunali321
2f3bb6cfe4 refactor: better wording. 2022-09-18 12:18:47 +05:30
oSumAtrIX
0e1fa1a5d6 build: bump patcher dependency 2022-09-18 08:38:03 +02:00
Alberto Ponces
38fb3444e4 build: Bump version to v0.0.12 2022-09-18 05:07:12 +01:00
Alberto Ponces
f10b5aebac fix: Decrease app name space a bit to prevent overflowing 2022-09-18 05:06:51 +01:00
Alberto Ponces
5a3884e159 fix: Fix uninstall apps 2022-09-18 05:06:23 +01:00
Alberto Ponces
41ac2b0df8 build: Bump version to v0.0.11 2022-09-18 04:47:23 +01:00
Alberto Ponces
52e7d76c9d fix: Fix spacing of App Info buttons 2022-09-18 04:46:21 +01:00
Alberto Ponces
3aa80cacc0 fix: Fix duplicate entries on non-root installations 2022-09-18 04:44:44 +01:00
Alberto Ponces
dd52c379b4 fix: Show updatable app item only on updatable apps listing 2022-09-18 04:12:33 +01:00
Alberto Ponces
5d073bddf2 fix: Minor i18n typo 2022-09-18 03:41:16 +01:00
Alberto Ponces
7d09169c06 build: Bump version to v0.0.10 2022-09-18 03:35:32 +01:00
Alberto Ponces
ce6f11f3f2 feat: Show recommendation version when possible 2022-09-18 03:14:48 +01:00
Alberto Ponces
d0fb6ac3c0 fix: Ignore empty results on root installed apps listing 2022-09-18 02:41:11 +01:00
Alberto Ponces
ae801a2918 fix: Fix apps reassess on root 2022-09-18 01:54:25 +01:00
Alberto Ponces
257fd46e27 fix: Improve quality of launcher and splash icons 2022-09-18 01:23:21 +01:00
Alberto Ponces
bb96c3ed63 fix: Make sure aapt lib gets extracted to native lib dir during install 2022-09-17 22:54:38 +01:00
Alberto Ponces
6bd218277d build: Bump version to v0.0.9 2022-09-17 19:40:44 +01:00
Alberto Ponces
83ad7605c4 fix: Minor improvements on App Info view 2022-09-17 19:30:04 +01:00
Alberto Ponces
5fbc8ff7a0 feat: Retrieve unsaved apps from manager's root folder and known packages from user's apps list 2022-09-17 19:29:46 +01:00
Alberto Ponces
bb4b59eee6 Disable permission_handler package for now 2022-09-17 17:42:29 +01:00
35 changed files with 572 additions and 219 deletions

View File

@@ -12,4 +12,8 @@ For suggestions and bug reports, open an issue [here](https://github.com/revance
If you wish to discuss the Manager, a thread has been made under the [#chat](https://discord.com/channels/952946952348270622/1002922226443632761) channel in the Discord server, please note that this thread may be temporary and may be removed in the future.
## ⚠️ Disclaimer
*Please note that even though we're releasing the Manager, it is an ALPHA version. Meaning there's a big chance that the Manager might not work at all for you.*
*Please note that even though we're releasing the Manager, it is an ALPHA version. Meaning there's a big chance that the Manager might not work at all for you.*
## Prerequisites
1. Android 8 or higher.
2. For YouTube and YouTube Music - Vanced MicroG(Only for non-root).

View File

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

View File

@@ -1,21 +1,23 @@
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
package="app.revanced.manager.flutter">
<uses-permission android:name="android.permission.FOREGROUND_SERVICE"/>
<uses-permission android:name="android.permission.INTERNET" />
<uses-permission android:name="android.permission.READ_MEDIA_IMAGES" />
<uses-permission android:name="android.permission.READ_MEDIA_VIDEO" />
<uses-permission android:name="android.permission.READ_MEDIA_AUDIO" />
<uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE" android:maxSdkVersion="32" />
<uses-permission android:name="android.permission.QUERY_ALL_PACKAGES" />
<uses-permission android:name="android.permission.REQUEST_INSTALL_PACKAGES" />
<uses-permission android:name="android.permission.WAKE_LOCK" />
<uses-permission android:name="android.permission.FOREGROUND_SERVICE"/>
<uses-permission android:name="android.permission.REQUEST_IGNORE_BATTERY_OPTIMIZATIONS" />
<uses-permission android:name="android.permission.REQUEST_INSTALL_PACKAGES" />
<uses-permission android:name="android.permission.REQUEST_DELETE_PACKAGES" />
<uses-permission android:name="android.permission.WAKE_LOCK" />
<application
android:label="ReVanced Manager"
android:name="${applicationName}"
android:icon="@mipmap/ic_launcher"
android:largeHeap="true">
android:largeHeap="true"
android:extractNativeLibs="true">
<activity
android:name=".MainActivity"
android:exported="true"

Binary file not shown.

After

Width:  |  Height:  |  Size: 28 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 4.4 KiB

After

Width:  |  Height:  |  Size: 3.1 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 2.6 KiB

After

Width:  |  Height:  |  Size: 1.9 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 6.4 KiB

After

Width:  |  Height:  |  Size: 4.5 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 10 KiB

After

Width:  |  Height:  |  Size: 7.4 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 14 KiB

After

Width:  |  Height:  |  Size: 11 KiB

View File

@@ -0,0 +1,19 @@
<?xml version="1.0" encoding="utf-8"?>
<resources>
<!-- Theme applied to the Android Window while the process is starting when the OS's Dark Mode setting is on -->
<style name="LaunchTheme" parent="@android:style/Theme.Black.NoTitleBar">
<!-- Show a splash screen on the activity. Automatically removed when
the Flutter engine draws its first frame -->
<item name="android:windowBackground">@drawable/launch_background</item>
<item name="android:windowSplashScreenAnimatedIcon">@drawable/ic_launcher_round</item>
</style>
<!-- Theme applied to the Android Window as soon as the process has started.
This theme determines the color of the Android Window while your
Flutter UI initializes, as well as behind your Flutter UI while its
running.
This Theme is only used starting with V2 of Flutter's Android embedding. -->
<style name="NormalTheme" parent="@android:style/Theme.Black.NoTitleBar">
<item name="android:windowBackground">?android:colorBackground</item>
</style>
</resources>

View File

@@ -0,0 +1,20 @@
<?xml version="1.0" encoding="utf-8"?>
<resources>
<!-- Theme applied to the Android Window while the process is starting when the OS's Dark Mode setting is off -->
<style name="LaunchTheme" parent="@android:style/Theme.Light.NoTitleBar">
<!-- Show a splash screen on the activity. Automatically removed when
the Flutter engine draws its first frame -->
<item name="android:windowBackground">@drawable/launch_background</item>
<item name="android:windowSplashScreenAnimatedIcon">@drawable/ic_launcher_round</item>
</style>
<!-- Theme applied to the Android Window as soon as the process has started.
This theme determines the color of the Android Window while your
Flutter UI initializes, as well as behind your Flutter UI while its
running.
This Theme is only used starting with V2 of Flutter's Android embedding. -->
<style name="NormalTheme" parent="@android:style/Theme.Light.NoTitleBar">
<item name="android:windowBackground">?android:colorBackground</item>
</style>
</resources>

View File

@@ -3,8 +3,8 @@
"cancelButton": "Cancel",
"enabledLabel": "Enabled",
"disabledLabel": "Disabled",
"yesLabel": "Yes",
"noLabel": "No",
"yesButton": "Yes",
"noButton": "No",
"navigationView": {
"dashboardTab": "Dashboard",
"patcherTab": "Patcher",
@@ -25,7 +25,7 @@
"downloadingMessage": "Downloading update!",
"installingMessage": "Installing update... Hang on!",
"errorDownloadMessage": "Unable to download update!",
"errorInstallMessage": "Unable to download update!",
"errorInstallMessage": "Unable to install update!",
"noConnection": "No internet connection"
},
"applicationItem": {
@@ -50,7 +50,10 @@
"widgetTitle": "Select application",
"widgetTitleSelected": "Selected application",
"widgetSubtitle": "No application selected.",
"noAppsLabel": "No applications found."
"noAppsLabel": "No applications found.",
"currentVersion": "Current",
"recommendedVersion": "Recommended",
"anyVersion": "any"
},
"patchSelectorCard": {
"widgetTitle": "Select patches",
@@ -75,7 +78,7 @@
"patchItem": {
"unsupportedWarningButton": "Unsupported version",
"unsupportedDialogTitle": "Warning",
"unsupportedDialogText": "Selecting this patch may or may not result in patching errors.\n\nApp version: {packageVersion}\nCurrent supported versions:\n{supportedVersions}"
"unsupportedDialogText": "Selecting this patch may result in patching errors.\n\nApp version: {packageVersion}\nCurrent supported versions:\n{supportedVersions}"
},
"installerView": {
"widgetTitle": "Installer",
@@ -86,7 +89,11 @@
"notificationTitle": "ReVanced Manager is patching",
"notificationText": "Tap to return to the installer",
"shareApkMenuOption": "Share APK",
"shareLogMenuOption": "Share log"
"shareLogMenuOption": "Share 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."
},
"settingsView": {
"widgetTitle": "Settings",
@@ -94,6 +101,7 @@
"patcherSectionTitle": "Patcher",
"teamSectionTitle": "Team",
"infoSectionTitle": "Info",
"advancedSectionTitle": "Advanced",
"darkThemeLabel": "Dark Mode",
"darkThemeHint": "Welcome to the Dark Side",
"dynamicThemeLabel": "Material You",
@@ -109,10 +117,14 @@
"sourcesIntegrationsLabel": "Integrations Source",
"sourcesResetDialogTitle": "Reset",
"sourcesResetDialogText": "Are you sure you want to reset custom sources to their default values?",
"apiURLResetDialogText": "Are you sure you want to reset API URL to their default values?",
"contributorsLabel": "Contributors",
"contributorsHint": "A list of contributors of ReVanced",
"logsLabel": "Logs",
"logsHint": "Share device debug logs",
"apiURLLabel": "API URL",
"apiURLHint": "Configure your custom API URL",
"selectApiURL": "Select URL",
"aboutLabel": "About",
"snackbarMessage": "Copied to clipboard"
},
@@ -122,9 +134,6 @@
"uninstallButton": "Uninstall",
"patchButton": "Patch",
"unpatchButton": "Unpatch",
"uninstallDialogTitle": "Uninstall",
"uninstallDialogText": "Are you sure you want to uninstall this app?",
"unpatchDialogTitle": "Unpatch",
"unpatchDialogText": "Are you sure you want to unpatch this app?",
"rootDialogTitle": "Error",
"rootDialogText": "App was installed with root mode enabled but currently root mode is disabled.\nPlease enable root mode first.",

Binary file not shown.

Before

Width:  |  Height:  |  Size: 6.2 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 14 KiB

View File

@@ -15,9 +15,10 @@ Future main() async {
await setupLocator();
WidgetsFlutterBinding.ensureInitialized();
await locator<ManagerAPI>().initialize();
await locator<PatcherAPI>().initialize();
locator<RevancedAPI>().initialize();
String apiUrl = locator<ManagerAPI>().getApiUrl();
await locator<RevancedAPI>().initialize(apiUrl);
locator<GithubAPI>().initialize();
await locator<PatcherAPI>().initialize();
runApp(const MyApp());
}

View File

@@ -17,6 +17,7 @@ class PatchedApplication {
Uint8List icon;
DateTime patchDate;
bool isRooted;
bool isFromStorage;
bool hasUpdates;
List<String> appliedPatches;
List<String> changelog;
@@ -29,6 +30,7 @@ class PatchedApplication {
required this.icon,
required this.patchDate,
this.isRooted = false,
this.isFromStorage = false,
this.hasUpdates = false,
this.appliedPatches = const [],
this.changelog = const [],

View File

@@ -9,8 +9,7 @@ import 'package:revanced_manager/models/patch.dart';
@lazySingleton
class GithubAPI {
final String apiUrl = 'https://api.github.com';
final Dio _dio = Dio();
final Dio _dio = Dio(BaseOptions(baseUrl: 'https://api.github.com'));
final DioCacheManager _dioCacheManager = DioCacheManager(CacheConfig());
final Options _cacheOptions = buildCacheOptions(
const Duration(days: 1),
@@ -37,7 +36,7 @@ class GithubAPI {
Future<Map<String, dynamic>?> _getLatestRelease(String repoName) async {
try {
var response = await _dio.get(
'$apiUrl/repos/$repoName/releases/latest',
'/repos/$repoName/releases/latest',
options: _cacheOptions,
);
return response.data;
@@ -55,7 +54,7 @@ class GithubAPI {
'src/main/kotlin/app/revanced/patches/${repoAppPath[packageName]}';
try {
var response = await _dio.get(
'$apiUrl/repos/$repoName/commits',
'/repos/$repoName/commits',
queryParameters: {
'path': path,
'per_page': 3,

View File

@@ -19,6 +19,7 @@ class ManagerAPI {
final String patcherRepo = 'revanced-patcher';
final String cliRepo = 'revanced-cli';
late SharedPreferences _prefs;
String defaultApiUrl = 'https://revanced-releases-api.afterst0rm.xyz';
String defaultPatcherRepo = 'revanced/revanced-patcher';
String defaultPatchesRepo = 'revanced/revanced-patches';
String defaultIntegrationsRepo = 'revanced/revanced-integrations';
@@ -29,6 +30,19 @@ class ManagerAPI {
_prefs = await SharedPreferences.getInstance();
}
String getApiUrl() {
return _prefs.getString('apiUrl') ?? defaultApiUrl;
}
Future<void> setApiUrl(String url) async {
if (url.isEmpty || url == ' ') {
url = defaultApiUrl;
}
await _revancedAPI.initialize(url);
await _revancedAPI.clearAllCache();
await _prefs.setString('apiUrl', url);
}
String getPatchesRepo() {
return _prefs.getString('patchesRepo') ?? defaultPatchesRepo;
}
@@ -110,10 +124,11 @@ class ManagerAPI {
}
Future<List<Patch>> getPatches() async {
if (getPatchesRepo() == defaultPatchesRepo) {
String repoName = getPatchesRepo();
if (repoName == defaultPatchesRepo) {
return await _revancedAPI.getPatches();
} else {
return await _githubAPI.getPatches(getPatchesRepo());
return await _githubAPI.getPatches(repoName);
}
}
@@ -165,46 +180,114 @@ class ManagerAPI {
return packageInfo.version;
}
Future<void> reAssessSavedApps() async {
List<PatchedApplication> patchedApps = getPatchedApps();
Future<List<PatchedApplication>> getAppsToRemove(
List<PatchedApplication> patchedApps,
) async {
List<PatchedApplication> toRemove = [];
for (PatchedApplication app in patchedApps) {
bool isRemove = await isAppUninstalled(app);
if (isRemove) {
toRemove.add(app);
} else {
app.hasUpdates = await hasAppUpdates(app.packageName, app.patchDate);
app.changelog = await getAppChangelog(app.packageName, app.patchDate);
if (!app.hasUpdates) {
String? currentInstalledVersion =
(await DeviceApps.getApp(app.packageName))?.versionName;
if (currentInstalledVersion != null) {
String currentSavedVersion = app.version;
int currentInstalledVersionInt = int.parse(
currentInstalledVersion.replaceAll(RegExp('[^0-9]'), ''));
int currentSavedVersionInt =
int.parse(currentSavedVersion.replaceAll(RegExp('[^0-9]'), ''));
if (currentInstalledVersionInt > currentSavedVersionInt) {
app.hasUpdates = true;
}
}
}
return toRemove;
}
Future<List<PatchedApplication>> getUnsavedApps(
List<PatchedApplication> patchedApps,
) async {
List<PatchedApplication> unsavedApps = [];
bool hasRootPermissions = await _rootAPI.hasRootPermissions();
if (hasRootPermissions) {
List<String> installedApps = await _rootAPI.getInstalledApps();
for (String packageName in installedApps) {
if (!patchedApps.any((app) => app.packageName == packageName)) {
ApplicationWithIcon? application =
await DeviceApps.getApp(packageName, true)
as ApplicationWithIcon?;
if (application != null) {
unsavedApps.add(
PatchedApplication(
name: application.appName,
packageName: application.packageName,
version: application.versionName!,
apkFilePath: application.apkFilePath,
icon: application.icon,
patchDate: DateTime.now(),
isRooted: true,
),
);
}
}
}
}
List<Application> userApps = await DeviceApps.getInstalledApplications(
includeSystemApps: false,
includeAppIcons: false,
);
for (Application app in userApps) {
if (app.packageName.startsWith('app.revanced') &&
!app.packageName.startsWith('app.revanced.manager.') &&
!patchedApps.any((uapp) => uapp.packageName == app.packageName)) {
ApplicationWithIcon? application =
await DeviceApps.getApp(app.packageName, true)
as ApplicationWithIcon?;
if (application != null) {
unsavedApps.add(
PatchedApplication(
name: application.appName,
packageName: application.packageName,
version: application.versionName!,
apkFilePath: application.apkFilePath,
icon: application.icon,
patchDate: DateTime.now(),
isRooted: false,
),
);
}
}
}
return unsavedApps;
}
Future<void> reAssessSavedApps() async {
List<PatchedApplication> patchedApps = getPatchedApps();
List<PatchedApplication> unsavedApps = await getUnsavedApps(patchedApps);
patchedApps.addAll(unsavedApps);
List<PatchedApplication> toRemove = await getAppsToRemove(patchedApps);
patchedApps.removeWhere((a) => toRemove.contains(a));
for (PatchedApplication app in patchedApps) {
app.hasUpdates = await hasAppUpdates(app.packageName, app.patchDate);
app.changelog = await getAppChangelog(app.packageName, app.patchDate);
if (!app.hasUpdates) {
String? currentInstalledVersion =
(await DeviceApps.getApp(app.packageName))?.versionName;
if (currentInstalledVersion != null) {
String currentSavedVersion = app.version;
int currentInstalledVersionInt = int.parse(
currentInstalledVersion.replaceAll(RegExp('[^0-9]'), ''));
int currentSavedVersionInt =
int.parse(currentSavedVersion.replaceAll(RegExp('[^0-9]'), ''));
if (currentInstalledVersionInt > currentSavedVersionInt) {
app.hasUpdates = true;
}
}
}
}
await setPatchedApps(patchedApps);
}
Future<bool> isAppUninstalled(PatchedApplication app) async {
bool existsRoot = false;
bool existsNonRoot = await DeviceApps.isAppInstalled(app.packageName);
if (app.isRooted) {
bool hasRootPermissions = await _rootAPI.hasRootPermissions();
if (hasRootPermissions) {
existsRoot = await _rootAPI.isAppInstalled(app.packageName);
}
return !existsRoot || !existsNonRoot;
}
bool existsNonRoot = await DeviceApps.isAppInstalled(app.packageName);
return !existsRoot && !existsNonRoot;
return !existsNonRoot;
}
Future<bool> hasAppUpdates(String packageName, DateTime patchDate) async {

View File

@@ -199,10 +199,9 @@ class PatcherAPI {
String prefix = appName.toLowerCase().replaceAll(' ', '-');
String newName = '$prefix-revanced_v$version.apk';
int lastSeparator = _outFile!.path.lastIndexOf('/');
File share = _outFile!.renameSync(
_outFile!.path.substring(0, lastSeparator + 1) + newName,
);
ShareExtend.share(share.path, 'file');
String newPath = _outFile!.path.substring(0, lastSeparator + 1) + newName;
File shareFile = _outFile!.copySync(newPath);
ShareExtend.share(shareFile.path, 'file');
}
}
@@ -220,4 +219,32 @@ class PatcherAPI {
log.writeAsStringSync(logs);
ShareExtend.share(log.path, 'file');
}
String getRecommendedVersion(String packageName) {
Map<String, int> versions = {};
for (Patch patch in _patches) {
Package? package = patch.compatiblePackages.firstWhereOrNull(
(pack) => pack.name == packageName,
);
if (package != null) {
for (String version in package.versions) {
versions.update(
version,
(value) => versions[version]! + 1,
ifAbsent: () => 1,
);
}
}
}
if (versions.isNotEmpty) {
var 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 '';
}
}

View File

@@ -9,15 +9,15 @@ import 'package:timeago/timeago.dart';
@lazySingleton
class RevancedAPI {
final String apiUrl = 'https://revanced-releases-api.afterst0rm.xyz';
final Dio _dio = Dio();
late Dio _dio = Dio();
final DioCacheManager _dioCacheManager = DioCacheManager(CacheConfig());
final Options _cacheOptions = buildCacheOptions(
const Duration(days: 1),
maxStale: const Duration(days: 7),
);
void initialize() {
Future<void> initialize(String apiUrl) async {
_dio = Dio(BaseOptions(baseUrl: apiUrl));
_dio.interceptors.add(_dioCacheManager.interceptor);
}
@@ -28,10 +28,7 @@ class RevancedAPI {
Future<Map<String, List<dynamic>>> getContributors() async {
Map<String, List<dynamic>> contributors = {};
try {
var response = await _dio.get(
'$apiUrl/contributors',
options: _cacheOptions,
);
var response = await _dio.get('/contributors', options: _cacheOptions);
List<dynamic> repositories = response.data['repositories'];
for (Map<String, dynamic> repo in repositories) {
String name = repo['name'];
@@ -45,7 +42,7 @@ class RevancedAPI {
Future<List<Patch>> getPatches() async {
try {
var response = await _dio.get('$apiUrl/patches', options: _cacheOptions);
var response = await _dio.get('/patches', options: _cacheOptions);
List<dynamic> patches = response.data;
return patches.map((patch) => Patch.fromJson(patch)).toList();
} on Exception {
@@ -58,7 +55,7 @@ class RevancedAPI {
String repoName,
) async {
try {
var response = await _dio.get('$apiUrl/tools', options: _cacheOptions);
var response = await _dio.get('/tools', options: _cacheOptions);
List<dynamic> tools = response.data['tools'];
return tools.firstWhereOrNull(
(t) =>
@@ -71,10 +68,14 @@ class RevancedAPI {
}
Future<String?> getLatestReleaseVersion(
String extension, String repoName) async {
String extension,
String repoName,
) async {
try {
Map<String, dynamic>? release =
await _getLatestRelease(extension, repoName);
Map<String, dynamic>? release = await _getLatestRelease(
extension,
repoName,
);
if (release != null) {
return release['version'];
}

View File

@@ -59,15 +59,8 @@ class RootAPI {
);
if (res != null) {
List<String> apps = res.split('\n');
List<String> toRemove = [];
for (String packageName in apps) {
bool isInstalled = await isAppInstalled(packageName);
if (!isInstalled) {
toRemove.add(packageName);
}
}
apps.removeWhere((a) => toRemove.contains(a));
return apps;
apps.removeWhere((pack) => pack.isEmpty);
return apps.map((pack) => pack.trim()).toList();
}
} on Exception {
return List.empty();

View File

@@ -54,6 +54,7 @@ class AppSelectorViewModel extends BaseViewModel {
apkFilePath: result.files.single.path!,
icon: application.icon,
patchDate: DateTime.now(),
isFromStorage: true,
);
locator<PatcherViewModel>().selectedPatches.clear();
locator<PatcherViewModel>().notifyListeners();

View File

@@ -1,6 +1,7 @@
import 'package:flutter/material.dart';
import 'package:flutter_i18n/flutter_i18n.dart';
import 'package:google_fonts/google_fonts.dart';
import 'package:animations/animations.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/homeView/available_updates_card.dart';
@@ -83,9 +84,26 @@ class HomeView extends StatelessWidget {
],
),
const SizedBox(height: 14),
model.showUpdatableApps
? AvailableUpdatesCard()
: InstalledAppsCard(),
PageTransitionSwitcher(
transitionBuilder:
(child, primaryAnimation, secondaryAnimation) {
return FadeThroughTransition(
animation: primaryAnimation,
secondaryAnimation: secondaryAnimation,
fillColor: Colors.transparent,
child: child,
);
},
layoutBuilder: (entries) {
return Stack(
alignment: Alignment.topCenter,
children: entries,
);
},
child: model.showUpdatableApps
? AvailableUpdatesCard()
: InstalledAppsCard(),
),
],
),
),

View File

@@ -74,7 +74,10 @@ class HomeViewModel extends BaseViewModel {
}
void _getPatchedApps() {
patchedInstalledApps = _managerAPI.getPatchedApps().toList();
patchedInstalledApps = _managerAPI
.getPatchedApps()
.where((app) => app.hasUpdates == false)
.toList();
patchedUpdatableApps = _managerAPI
.getPatchedApps()
.where((app) => app.hasUpdates == true)
@@ -169,11 +172,11 @@ class HomeViewModel extends BaseViewModel {
actions: <Widget>[
CustomMaterialButton(
isFilled: false,
label: I18nText('cancelButton'),
label: I18nText('noButton'),
onPressed: () => Navigator.of(context).pop(),
),
CustomMaterialButton(
label: I18nText('okButton'),
label: I18nText('yesButton'),
onPressed: () {
Navigator.of(context).pop();
updateManager(context);

View File

@@ -111,7 +111,10 @@ class InstallerView extends StatelessWidget {
label:
I18nText('installerView.installRootButton'),
isExpanded: true,
onPressed: () => model.installResult(true),
onPressed: () => model.installResult(
context,
true,
),
),
),
Visibility(
@@ -125,7 +128,10 @@ class InstallerView extends StatelessWidget {
child: CustomMaterialButton(
label: I18nText('installerView.installButton'),
isExpanded: true,
onPressed: () => model.installResult(false),
onPressed: () => model.installResult(
context,
false,
),
),
),
],

View File

@@ -10,6 +10,7 @@ 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/views/patcher/patcher_viewmodel.dart';
import 'package:revanced_manager/ui/widgets/installerView/custom_material_button.dart';
import 'package:stacked/stacked.dart';
import 'package:wakelock/wakelock.dart';
@@ -32,7 +33,7 @@ class InstallerViewModel extends BaseViewModel {
Future<void> initialize(BuildContext context) async {
if (await Permission.ignoreBatteryOptimizations.isGranted) {
try {
await FlutterBackground.initialize(
FlutterBackground.initialize(
androidConfig: FlutterBackgroundAndroidConfig(
notificationTitle: FlutterI18n.translate(
context,
@@ -48,8 +49,7 @@ class InstallerViewModel extends BaseViewModel {
defType: 'drawable',
),
),
);
await FlutterBackground.enableBackgroundExecution();
).then((value) => FlutterBackground.enableBackgroundExecution());
} on Exception {
// ignore
}
@@ -122,9 +122,9 @@ class InstallerViewModel extends BaseViewModel {
hasErrors = true;
update(-1.0, 'Aborting...', 'No app or patches selected! Aborting');
}
if (await Permission.ignoreBatteryOptimizations.isGranted) {
if (FlutterBackground.isBackgroundExecutionEnabled) {
try {
await FlutterBackground.disableBackgroundExecution();
FlutterBackground.disableBackgroundExecution();
} on Exception {
// ignore
}
@@ -133,28 +133,56 @@ class InstallerViewModel extends BaseViewModel {
isPatching = false;
}
void installResult(bool installAsRoot) async {
void installResult(BuildContext context, bool installAsRoot) async {
_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) {
update(1.0, 'Installed!', 'Installed!');
_app.patchDate = DateTime.now();
_app.appliedPatches = _patches.map((p) => p.name).toList();
bool hasMicroG = _patches.any((p) => p.name.endsWith('microg-support'));
if (hasMicroG) {
_app.packageName = _app.packageName.replaceFirst(
'com.google.',
'app.revanced.',
);
bool hasMicroG = _patches.any((p) => p.name.endsWith('microg-support'));
bool rootMicroG = installAsRoot && hasMicroG;
bool rootFromStorage = installAsRoot && _app.isFromStorage;
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(
1.0,
'Installing...',
_app.isRooted
? 'Installing patched file using root method'
: 'Installing patched file using nonroot method',
);
isInstalled = await _patcherAPI.installPatchedFile(_app);
if (isInstalled) {
update(1.0, 'Installed!', 'Installed!');
_app.isFromStorage = false;
_app.patchDate = DateTime.now();
_app.appliedPatches = _patches.map((p) => p.name).toList();
if (hasMicroG) {
_app.packageName = _app.packageName.replaceFirst(
'com.google.',
'app.revanced.',
);
}
await _managerAPI.savePatchedApp(_app);
}
await _managerAPI.savePatchedApp(_app);
}
}

View File

@@ -15,28 +15,30 @@ import 'package:stacked/stacked.dart';
class NavigationViewModel extends IndexTrackingViewModel {
void initialize(BuildContext context) async {
SharedPreferences prefs = await SharedPreferences.getInstance();
if (prefs.getBool('permissionsRequested') == null) {
await prefs.setBool('permissionsRequested', true);
RootAPI().hasRootPermissions().then(
(value) => Permission.requestInstallPackages.request().then(
(value) => Permission.ignoreBatteryOptimizations.request(),
),
);
}
if (prefs.getBool('useDarkTheme') == null) {
bool isDark =
MediaQuery.of(context).platformBrightness != Brightness.light;
await prefs.setBool('useDarkTheme', isDark);
await DynamicTheme.of(context)!.setTheme(isDark ? 1 : 0);
}
SystemChrome.setEnabledSystemUIMode(SystemUiMode.edgeToEdge);
SystemChrome.setSystemUIOverlayStyle(
SystemUiOverlayStyle(
systemNavigationBarColor:
DynamicTheme.of(context)!.theme.colorScheme.surface,
systemNavigationBarColor: Colors.transparent,
systemNavigationBarIconBrightness:
DynamicTheme.of(context)!.theme.brightness == Brightness.light
? Brightness.dark
: Brightness.light,
),
);
if (prefs.getBool('permissionsRequested') == null) {
await prefs.setBool('permissionsRequested', true);
RootAPI().hasRootPermissions();
Permission.requestInstallPackages.request();
Permission.ignoreBatteryOptimizations.request();
}
}
Widget getViewForIndex(int index) {

View File

@@ -64,11 +64,11 @@ class PatcherViewModel extends BaseViewModel {
actions: <Widget>[
CustomMaterialButton(
isFilled: false,
label: I18nText('cancelButton'),
label: I18nText('noButton'),
onPressed: () => Navigator.of(context).pop(),
),
CustomMaterialButton(
label: I18nText('okButton'),
label: I18nText('yesButton'),
onPressed: () {
Navigator.of(context).pop();
navigateToInstaller();
@@ -79,4 +79,32 @@ class PatcherViewModel extends BaseViewModel {
);
}
}
String getAppSelectionString() {
String text = '${selectedApp!.name} (${selectedApp!.packageName})';
if (text.length > 32) {
text = '${text.substring(0, 32)}...)';
}
return text;
}
String getRecommendedVersionString(BuildContext context) {
String recommendedVersion =
_patcherAPI.getRecommendedVersion(selectedApp!.packageName);
if (recommendedVersion.isEmpty) {
recommendedVersion = FlutterI18n.translate(
context,
'appSelectorCard.anyVersion',
);
} else {
recommendedVersion = 'v$recommendedVersion';
}
return '${FlutterI18n.translate(
context,
'appSelectorCard.currentVersion',
)}: v${selectedApp!.version}\n${FlutterI18n.translate(
context,
'appSelectorCard.recommendedVersion',
)}: $recommendedVersion';
}
}

View File

@@ -145,6 +145,17 @@ class SettingsView extends StatelessWidget {
const AboutWidget(),
],
),
const Divider(thickness: 1.0),
SettingsSection(
title: 'settingsView.advancedSectionTitle',
children: <Widget>[
SettingsTileDialog(
title: 'settingsView.apiURLLabel',
subtitle: 'settingsView.apiURLHint',
onTap: () => model.showApiUrlDialog(context),
),
],
),
],
),
),

View File

@@ -27,6 +27,7 @@ class SettingsViewModel extends BaseViewModel {
final TextEditingController _patSourceController = TextEditingController();
final TextEditingController _orgIntSourceController = TextEditingController();
final TextEditingController _intSourceController = TextEditingController();
final TextEditingController _apiUrlController = TextEditingController();
void setLanguage(String language) {
notifyListeners();
@@ -55,12 +56,6 @@ class SettingsViewModel extends BaseViewModel {
} else {
await DynamicTheme.of(context)!.setTheme(value ? 3 : 1);
}
SystemChrome.setSystemUIOverlayStyle(
SystemUiOverlayStyle(
systemNavigationBarColor:
DynamicTheme.of(context)!.theme.colorScheme.surface,
),
);
notifyListeners();
}
@@ -78,8 +73,6 @@ class SettingsViewModel extends BaseViewModel {
}
SystemChrome.setSystemUIOverlayStyle(
SystemUiOverlayStyle(
systemNavigationBarColor:
DynamicTheme.of(context)!.theme.colorScheme.surface,
systemNavigationBarIconBrightness:
value ? Brightness.light : Brightness.dark,
),
@@ -208,6 +201,65 @@ class SettingsViewModel extends BaseViewModel {
);
}
Future<void> showApiUrlDialog(BuildContext context) async {
String apiUrl = _managerAPI.getApiUrl();
_apiUrlController.text = apiUrl.replaceAll('https://', '');
return showDialog(
context: context,
builder: (context) => AlertDialog(
title: Row(
children: <Widget>[
I18nText('settingsView.apiURLLabel'),
const Spacer(),
IconButton(
icon: const Icon(Icons.manage_history_outlined),
onPressed: () => showApiUrlResetDialog(context),
color: Theme.of(context).colorScheme.secondary,
)
],
),
backgroundColor: Theme.of(context).colorScheme.secondaryContainer,
content: SingleChildScrollView(
child: Column(
children: <Widget>[
CustomTextField(
leadingIcon: Icon(
Icons.api_outlined,
color: Theme.of(context).colorScheme.secondary,
),
inputController: _apiUrlController,
label: I18nText('settingsView.selectApiURL'),
hint: apiUrl.split('/')[0],
onChanged: (value) => notifyListeners(),
),
],
),
),
actions: <Widget>[
CustomMaterialButton(
isFilled: false,
label: I18nText('cancelButton'),
onPressed: () {
_apiUrlController.clear();
Navigator.of(context).pop();
},
),
CustomMaterialButton(
label: I18nText('okButton'),
onPressed: () {
String apiUrl = _apiUrlController.text;
if (!apiUrl.startsWith('https')) {
apiUrl = 'https://$apiUrl';
}
_managerAPI.setApiUrl(apiUrl);
Navigator.of(context).pop();
},
)
],
),
);
}
Future<void> showResetConfirmationDialog(BuildContext context) async {
return showDialog(
context: context,
@@ -218,11 +270,11 @@ class SettingsViewModel extends BaseViewModel {
actions: <Widget>[
CustomMaterialButton(
isFilled: false,
label: I18nText('cancelButton'),
label: I18nText('noButton'),
onPressed: () => Navigator.of(context).pop(),
),
CustomMaterialButton(
label: I18nText('okButton'),
label: I18nText('yesButton'),
onPressed: () {
_managerAPI.setPatchesRepo('');
_managerAPI.setIntegrationsRepo('');
@@ -235,6 +287,32 @@ class SettingsViewModel extends BaseViewModel {
);
}
Future<void> showApiUrlResetDialog(BuildContext context) async {
return showDialog(
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'),
onPressed: () => Navigator.of(context).pop(),
),
CustomMaterialButton(
label: I18nText('yesButton'),
onPressed: () {
_managerAPI.setApiUrl('');
Navigator.of(context).pop();
Navigator.of(context).pop();
},
)
],
),
);
}
Future<int> getSdkVersion() async {
AndroidDeviceInfo info = await DeviceInfoPlugin().androidInfo;
return info.version.sdkInt ?? -1;

View File

@@ -64,8 +64,9 @@ class AppInfoView extends StatelessWidget {
CustomCard(
child: IntrinsicHeight(
child: Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
mainAxisAlignment: MainAxisAlignment.center,
children: <Widget>[
!app.isRooted ? const Spacer() : Container(),
InkWell(
onTap: () => model.openApp(app),
child: Column(
@@ -92,11 +93,13 @@ class AppInfoView extends StatelessWidget {
],
),
),
const Spacer(),
VerticalDivider(
color: Theme.of(context).canvasColor,
),
const Spacer(),
InkWell(
onTap: () => model.showUninstallAlertDialog(
onTap: () => model.showUninstallDialog(
context,
app,
false,
@@ -125,9 +128,11 @@ class AppInfoView extends StatelessWidget {
],
),
),
const Spacer(),
VerticalDivider(
color: Theme.of(context).canvasColor,
),
const Spacer(),
InkWell(
onTap: () {
model.navigateToPatcher(app);
@@ -157,45 +162,49 @@ class AppInfoView extends StatelessWidget {
],
),
),
Visibility(
visible: app.isRooted,
child: VerticalDivider(
color: Theme.of(context).canvasColor,
),
),
Visibility(
visible: app.isRooted,
child: InkWell(
onTap: () => model.showUninstallAlertDialog(
context,
app,
true,
),
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
children: <Widget>[
Icon(
Icons.settings_backup_restore_outlined,
color:
Theme.of(context).colorScheme.primary,
app.isRooted ? const Spacer() : Container(),
app.isRooted
? VerticalDivider(
color: Theme.of(context).canvasColor,
)
: Container(),
app.isRooted ? const Spacer() : Container(),
app.isRooted
? InkWell(
onTap: () => model.showUninstallDialog(
context,
app,
true,
),
const SizedBox(height: 10),
I18nText(
'appInfoView.unpatchButton',
child: Text(
'',
style: TextStyle(
child: Column(
mainAxisAlignment:
MainAxisAlignment.center,
children: <Widget>[
Icon(
Icons
.settings_backup_restore_outlined,
color: Theme.of(context)
.colorScheme
.primary,
fontWeight: FontWeight.bold,
),
),
const SizedBox(height: 10),
I18nText(
'appInfoView.unpatchButton',
child: Text(
'',
style: TextStyle(
color: Theme.of(context)
.colorScheme
.primary,
fontWeight: FontWeight.bold,
),
),
),
],
),
],
),
),
),
)
: Container(),
!app.isRooted ? const Spacer() : Container(),
],
),
),

View File

@@ -1,3 +1,4 @@
// ignore_for_file: use_build_context_synchronously
import 'package:device_apps/device_apps.dart';
import 'package:flutter/material.dart';
import 'package:flutter_i18n/flutter_i18n.dart';
@@ -30,8 +31,9 @@ class AppInfoViewModel extends BaseViewModel {
}
}
} else {
DeviceApps.uninstallApp(app.packageName);
_managerAPI.deletePatchedApp(app);
DeviceApps.uninstallApp(app.packageName).then(
(value) => _managerAPI.deletePatchedApp(app),
);
}
}
@@ -43,7 +45,7 @@ class AppInfoViewModel extends BaseViewModel {
locator<NavigationViewModel>().setIndex(1);
}
Future<void> showUninstallAlertDialog(
Future<void> showUninstallDialog(
BuildContext context,
PatchedApplication app,
bool onlyUnpatch,
@@ -65,38 +67,40 @@ class AppInfoViewModel extends BaseViewModel {
),
);
} else {
return showDialog(
context: context,
builder: (context) => AlertDialog(
title: I18nText(
onlyUnpatch
? 'appInfoView.unpatchDialogTitle'
: 'appInfoView.uninstallDialogTitle',
),
backgroundColor: Theme.of(context).colorScheme.secondaryContainer,
content: I18nText(
onlyUnpatch
? 'appInfoView.unpatchDialogText'
: 'appInfoView.uninstallDialogText',
),
actions: <Widget>[
CustomMaterialButton(
isFilled: false,
label: I18nText('cancelButton'),
onPressed: () => Navigator.of(context).pop(),
if (onlyUnpatch) {
return showDialog(
context: context,
builder: (context) => AlertDialog(
title: I18nText(
'appInfoView.unpatchButton',
),
CustomMaterialButton(
label: I18nText('okButton'),
onPressed: () {
uninstallApp(app, onlyUnpatch);
locator<HomeViewModel>().initialize(context);
Navigator.of(context).pop();
Navigator.of(context).pop();
},
)
],
),
);
backgroundColor: Theme.of(context).colorScheme.secondaryContainer,
content: I18nText(
'appInfoView.unpatchDialogText',
),
actions: <Widget>[
CustomMaterialButton(
isFilled: false,
label: I18nText('noButton'),
onPressed: () => Navigator.of(context).pop(),
),
CustomMaterialButton(
label: I18nText('yesButton'),
onPressed: () {
uninstallApp(app, onlyUnpatch);
locator<HomeViewModel>().initialize(context);
Navigator.of(context).pop();
Navigator.of(context).pop();
},
)
],
),
);
} else {
uninstallApp(app, onlyUnpatch);
locator<HomeViewModel>().initialize(context);
Navigator.of(context).pop();
}
}
}

View File

@@ -39,7 +39,7 @@ class AppSelectorCard extends StatelessWidget {
: Row(
children: <Widget>[
SizedBox(
height: 16.0,
height: 18.0,
child: ClipOval(
child: Image.memory(
locator<PatcherViewModel>().selectedApp == null
@@ -49,8 +49,23 @@ class AppSelectorCard extends StatelessWidget {
),
),
),
const SizedBox(width: 4),
Text(_getAppSelection()),
const SizedBox(width: 6),
Text(locator<PatcherViewModel>().getAppSelectionString()),
],
),
locator<PatcherViewModel>().selectedApp == null
? Container()
: Column(
children: [
const SizedBox(height: 10),
Padding(
padding: const EdgeInsets.only(left: 20),
child: Text(
locator<PatcherViewModel>()
.getRecommendedVersionString(context),
style: const TextStyle(fontStyle: FontStyle.italic),
),
),
],
),
],
@@ -58,10 +73,4 @@ class AppSelectorCard extends StatelessWidget {
),
);
}
String _getAppSelection() {
String name = locator<PatcherViewModel>().selectedApp!.name;
String version = locator<PatcherViewModel>().selectedApp!.version;
return '$name (v$version)';
}
}

View File

@@ -70,25 +70,28 @@ class _ApplicationItemState extends State<ApplicationItem>
const SizedBox(width: 4),
Padding(
padding: const EdgeInsets.only(left: 15.0),
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: <Widget>[
Text(
widget.name,
style: const TextStyle(
fontSize: 16,
fontWeight: FontWeight.w500,
),
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: <Widget>[
Text(
widget.name.length > 9
? '${widget.name.substring(0, 9)}...'
: widget.name,
style: const TextStyle(
fontSize: 16,
fontWeight: FontWeight.w500,
),
Text(format(widget.patchDate)),
],
),
),
Text(format(widget.patchDate)),
],
),
),
const Spacer(),
Padding(
padding: const EdgeInsets.only(right: 5.0),
child: RotationTransition(
turns: Tween(begin: 0.0, end: 0.50).animate(_animationController),
turns:
Tween(begin: 0.0, end: 0.50).animate(_animationController),
child: IconButton(
onPressed: () {
expController.toggle();

View File

@@ -4,7 +4,7 @@ homepage: https://github.com/revanced/revanced-manager
publish_to: 'none'
version: 0.0.8+8
version: 0.0.14+14
environment:
sdk: ">=2.17.5 <3.0.0"
@@ -75,13 +75,6 @@ dev_dependencies:
injectable_generator: ^1.5.4
json_serializable: ^6.3.1
flutter_icons:
android: true
ios: false
image_path: "assets/images/ic_launcher_round.png"
adaptive_icon_background: "#1B1B1B"
adaptive_icon_foreground: "assets/images/ic_launcher.png"
flutter:
uses-material-design: true
assets: