Compare commits

...

23 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
23 changed files with 356 additions and 167 deletions

View File

@@ -13,3 +13,7 @@ If you wish to discuss the Manager, a thread has been made under the [#chat](htt
## ⚠️ Disclaimer ## ⚠️ 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" implementation "org.jetbrains.kotlin:kotlin-stdlib-jdk7:$kotlin_version"
// ReVanced // ReVanced
implementation "app.revanced:revanced-patcher:4.4.1" implementation "app.revanced:revanced-patcher:4.4.2"
// Signing & aligning // Signing & aligning
implementation("org.bouncycastle:bcpkix-jdk15on:1.70") implementation("org.bouncycastle:bcpkix-jdk15on:1.70")

View File

@@ -1,15 +1,16 @@
<manifest xmlns:android="http://schemas.android.com/apk/res/android" <manifest xmlns:android="http://schemas.android.com/apk/res/android"
package="app.revanced.manager.flutter"> 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.INTERNET" />
<uses-permission android:name="android.permission.READ_MEDIA_IMAGES" /> <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_VIDEO" />
<uses-permission android:name="android.permission.READ_MEDIA_AUDIO" /> <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.READ_EXTERNAL_STORAGE" android:maxSdkVersion="32" />
<uses-permission android:name="android.permission.QUERY_ALL_PACKAGES" /> <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_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 <application
android:label="ReVanced Manager" android:label="ReVanced Manager"

View File

@@ -3,8 +3,8 @@
"cancelButton": "Cancel", "cancelButton": "Cancel",
"enabledLabel": "Enabled", "enabledLabel": "Enabled",
"disabledLabel": "Disabled", "disabledLabel": "Disabled",
"yesLabel": "Yes", "yesButton": "Yes",
"noLabel": "No", "noButton": "No",
"navigationView": { "navigationView": {
"dashboardTab": "Dashboard", "dashboardTab": "Dashboard",
"patcherTab": "Patcher", "patcherTab": "Patcher",
@@ -25,7 +25,7 @@
"downloadingMessage": "Downloading update!", "downloadingMessage": "Downloading update!",
"installingMessage": "Installing update... Hang on!", "installingMessage": "Installing update... Hang on!",
"errorDownloadMessage": "Unable to download update!", "errorDownloadMessage": "Unable to download update!",
"errorInstallMessage": "Unable to download update!", "errorInstallMessage": "Unable to install update!",
"noConnection": "No internet connection" "noConnection": "No internet connection"
}, },
"applicationItem": { "applicationItem": {
@@ -78,7 +78,7 @@
"patchItem": { "patchItem": {
"unsupportedWarningButton": "Unsupported version", "unsupportedWarningButton": "Unsupported version",
"unsupportedDialogTitle": "Warning", "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": { "installerView": {
"widgetTitle": "Installer", "widgetTitle": "Installer",
@@ -89,7 +89,11 @@
"notificationTitle": "ReVanced Manager is patching", "notificationTitle": "ReVanced Manager is patching",
"notificationText": "Tap to return to the installer", "notificationText": "Tap to return to the installer",
"shareApkMenuOption": "Share APK", "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": { "settingsView": {
"widgetTitle": "Settings", "widgetTitle": "Settings",
@@ -97,6 +101,7 @@
"patcherSectionTitle": "Patcher", "patcherSectionTitle": "Patcher",
"teamSectionTitle": "Team", "teamSectionTitle": "Team",
"infoSectionTitle": "Info", "infoSectionTitle": "Info",
"advancedSectionTitle": "Advanced",
"darkThemeLabel": "Dark Mode", "darkThemeLabel": "Dark Mode",
"darkThemeHint": "Welcome to the Dark Side", "darkThemeHint": "Welcome to the Dark Side",
"dynamicThemeLabel": "Material You", "dynamicThemeLabel": "Material You",
@@ -112,10 +117,14 @@
"sourcesIntegrationsLabel": "Integrations Source", "sourcesIntegrationsLabel": "Integrations Source",
"sourcesResetDialogTitle": "Reset", "sourcesResetDialogTitle": "Reset",
"sourcesResetDialogText": "Are you sure you want to reset custom sources to their default values?", "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", "contributorsLabel": "Contributors",
"contributorsHint": "A list of contributors of ReVanced", "contributorsHint": "A list of contributors of ReVanced",
"logsLabel": "Logs", "logsLabel": "Logs",
"logsHint": "Share device debug logs", "logsHint": "Share device debug logs",
"apiURLLabel": "API URL",
"apiURLHint": "Configure your custom API URL",
"selectApiURL": "Select URL",
"aboutLabel": "About", "aboutLabel": "About",
"snackbarMessage": "Copied to clipboard" "snackbarMessage": "Copied to clipboard"
}, },
@@ -125,9 +134,6 @@
"uninstallButton": "Uninstall", "uninstallButton": "Uninstall",
"patchButton": "Patch", "patchButton": "Patch",
"unpatchButton": "Unpatch", "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?", "unpatchDialogText": "Are you sure you want to unpatch this app?",
"rootDialogTitle": "Error", "rootDialogTitle": "Error",
"rootDialogText": "App was installed with root mode enabled but currently root mode is disabled.\nPlease enable root mode first.", "rootDialogText": "App was installed with root mode enabled but currently root mode is disabled.\nPlease enable root mode first.",

View File

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

View File

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

View File

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

View File

@@ -19,6 +19,7 @@ class ManagerAPI {
final String patcherRepo = 'revanced-patcher'; final String patcherRepo = 'revanced-patcher';
final String cliRepo = 'revanced-cli'; final String cliRepo = 'revanced-cli';
late SharedPreferences _prefs; late SharedPreferences _prefs;
String defaultApiUrl = 'https://revanced-releases-api.afterst0rm.xyz';
String defaultPatcherRepo = 'revanced/revanced-patcher'; String defaultPatcherRepo = 'revanced/revanced-patcher';
String defaultPatchesRepo = 'revanced/revanced-patches'; String defaultPatchesRepo = 'revanced/revanced-patches';
String defaultIntegrationsRepo = 'revanced/revanced-integrations'; String defaultIntegrationsRepo = 'revanced/revanced-integrations';
@@ -29,6 +30,19 @@ class ManagerAPI {
_prefs = await SharedPreferences.getInstance(); _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() { String getPatchesRepo() {
return _prefs.getString('patchesRepo') ?? defaultPatchesRepo; return _prefs.getString('patchesRepo') ?? defaultPatchesRepo;
} }
@@ -110,10 +124,11 @@ class ManagerAPI {
} }
Future<List<Patch>> getPatches() async { Future<List<Patch>> getPatches() async {
if (getPatchesRepo() == defaultPatchesRepo) { String repoName = getPatchesRepo();
if (repoName == defaultPatchesRepo) {
return await _revancedAPI.getPatches(); return await _revancedAPI.getPatches();
} else { } else {
return await _githubAPI.getPatches(getPatchesRepo()); return await _githubAPI.getPatches(repoName);
} }
} }
@@ -212,7 +227,8 @@ class ManagerAPI {
); );
for (Application app in userApps) { for (Application app in userApps) {
if (app.packageName.startsWith('app.revanced') && if (app.packageName.startsWith('app.revanced') &&
!app.packageName.startsWith('app.revanced.manager.')) { !app.packageName.startsWith('app.revanced.manager.') &&
!patchedApps.any((uapp) => uapp.packageName == app.packageName)) {
ApplicationWithIcon? application = ApplicationWithIcon? application =
await DeviceApps.getApp(app.packageName, true) await DeviceApps.getApp(app.packageName, true)
as ApplicationWithIcon?; as ApplicationWithIcon?;

View File

@@ -199,10 +199,9 @@ class PatcherAPI {
String prefix = appName.toLowerCase().replaceAll(' ', '-'); String prefix = appName.toLowerCase().replaceAll(' ', '-');
String newName = '$prefix-revanced_v$version.apk'; String newName = '$prefix-revanced_v$version.apk';
int lastSeparator = _outFile!.path.lastIndexOf('/'); int lastSeparator = _outFile!.path.lastIndexOf('/');
File share = _outFile!.renameSync( String newPath = _outFile!.path.substring(0, lastSeparator + 1) + newName;
_outFile!.path.substring(0, lastSeparator + 1) + newName, File shareFile = _outFile!.copySync(newPath);
); ShareExtend.share(shareFile.path, 'file');
ShareExtend.share(share.path, 'file');
} }
} }

View File

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

View File

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

View File

@@ -1,6 +1,7 @@
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:google_fonts/google_fonts.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/app/app.locator.dart';
import 'package:revanced_manager/ui/views/home/home_viewmodel.dart'; import 'package:revanced_manager/ui/views/home/home_viewmodel.dart';
import 'package:revanced_manager/ui/widgets/homeView/available_updates_card.dart'; import 'package:revanced_manager/ui/widgets/homeView/available_updates_card.dart';
@@ -83,9 +84,26 @@ class HomeView extends StatelessWidget {
], ],
), ),
const SizedBox(height: 14), const SizedBox(height: 14),
model.showUpdatableApps PageTransitionSwitcher(
? AvailableUpdatesCard() transitionBuilder:
: InstalledAppsCard(), (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() { void _getPatchedApps() {
patchedInstalledApps = _managerAPI.getPatchedApps().toList(); patchedInstalledApps = _managerAPI
.getPatchedApps()
.where((app) => app.hasUpdates == false)
.toList();
patchedUpdatableApps = _managerAPI patchedUpdatableApps = _managerAPI
.getPatchedApps() .getPatchedApps()
.where((app) => app.hasUpdates == true) .where((app) => app.hasUpdates == true)
@@ -169,11 +172,11 @@ class HomeViewModel extends BaseViewModel {
actions: <Widget>[ actions: <Widget>[
CustomMaterialButton( CustomMaterialButton(
isFilled: false, isFilled: false,
label: I18nText('cancelButton'), label: I18nText('noButton'),
onPressed: () => Navigator.of(context).pop(), onPressed: () => Navigator.of(context).pop(),
), ),
CustomMaterialButton( CustomMaterialButton(
label: I18nText('okButton'), label: I18nText('yesButton'),
onPressed: () { onPressed: () {
Navigator.of(context).pop(); Navigator.of(context).pop();
updateManager(context); updateManager(context);

View File

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

View File

@@ -3,13 +3,14 @@ import 'package:flutter/material.dart';
import 'package:flutter/services.dart'; import 'package:flutter/services.dart';
import 'package:flutter_background/flutter_background.dart'; import 'package:flutter_background/flutter_background.dart';
import 'package:flutter_i18n/flutter_i18n.dart'; import 'package:flutter_i18n/flutter_i18n.dart';
//import 'package:permission_handler/permission_handler.dart'; import 'package:permission_handler/permission_handler.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/models/patch.dart';
import 'package:revanced_manager/models/patched_application.dart'; import 'package:revanced_manager/models/patched_application.dart';
import 'package:revanced_manager/services/manager_api.dart'; import 'package:revanced_manager/services/manager_api.dart';
import 'package:revanced_manager/services/patcher_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/views/patcher/patcher_viewmodel.dart';
import 'package:revanced_manager/ui/widgets/installerView/custom_material_button.dart';
import 'package:stacked/stacked.dart'; import 'package:stacked/stacked.dart';
import 'package:wakelock/wakelock.dart'; import 'package:wakelock/wakelock.dart';
@@ -30,9 +31,9 @@ class InstallerViewModel extends BaseViewModel {
bool hasErrors = false; bool hasErrors = false;
Future<void> initialize(BuildContext context) async { Future<void> initialize(BuildContext context) async {
if (true /*await Permission.ignoreBatteryOptimizations.isGranted*/) { if (await Permission.ignoreBatteryOptimizations.isGranted) {
try { try {
await FlutterBackground.initialize( FlutterBackground.initialize(
androidConfig: FlutterBackgroundAndroidConfig( androidConfig: FlutterBackgroundAndroidConfig(
notificationTitle: FlutterI18n.translate( notificationTitle: FlutterI18n.translate(
context, context,
@@ -48,8 +49,7 @@ class InstallerViewModel extends BaseViewModel {
defType: 'drawable', defType: 'drawable',
), ),
), ),
); ).then((value) => FlutterBackground.enableBackgroundExecution());
await FlutterBackground.enableBackgroundExecution();
} on Exception { } on Exception {
// ignore // ignore
} }
@@ -122,9 +122,9 @@ class InstallerViewModel extends BaseViewModel {
hasErrors = true; hasErrors = true;
update(-1.0, 'Aborting...', 'No app or patches selected! Aborting'); update(-1.0, 'Aborting...', 'No app or patches selected! Aborting');
} }
if (true /*await Permission.ignoreBatteryOptimizations.isGranted*/) { if (FlutterBackground.isBackgroundExecutionEnabled) {
try { try {
await FlutterBackground.disableBackgroundExecution(); FlutterBackground.disableBackgroundExecution();
} on Exception { } on Exception {
// ignore // ignore
} }
@@ -133,28 +133,56 @@ class InstallerViewModel extends BaseViewModel {
isPatching = false; isPatching = false;
} }
void installResult(bool installAsRoot) async { void installResult(BuildContext context, bool installAsRoot) async {
_app.isRooted = installAsRoot; _app.isRooted = installAsRoot;
update( bool hasMicroG = _patches.any((p) => p.name.endsWith('microg-support'));
1.0, bool rootMicroG = installAsRoot && hasMicroG;
'Installing...', bool rootFromStorage = installAsRoot && _app.isFromStorage;
_app.isRooted bool ytWithoutRootMicroG =
? 'Installing patched file using root method' !installAsRoot && !hasMicroG && _app.packageName.contains('youtube');
: 'Installing patched file using nonroot method', if (rootMicroG || rootFromStorage || ytWithoutRootMicroG) {
); return showDialog(
isInstalled = await _patcherAPI.installPatchedFile(_app); context: context,
if (isInstalled) { builder: (context) => AlertDialog(
update(1.0, 'Installed!', 'Installed!'); title: I18nText('installerView.installErrorDialogTitle'),
_app.patchDate = DateTime.now(); backgroundColor: Theme.of(context).colorScheme.secondaryContainer,
_app.appliedPatches = _patches.map((p) => p.name).toList(); content: I18nText(
bool hasMicroG = _patches.any((p) => p.name.endsWith('microg-support')); rootMicroG
if (hasMicroG) { ? 'installerView.installErrorDialogText1'
_app.packageName = _app.packageName.replaceFirst( : rootFromStorage
'com.google.', ? 'installerView.installErrorDialogText3'
'app.revanced.', : '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

@@ -3,7 +3,7 @@ import 'package:dynamic_themes/dynamic_themes.dart';
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import 'package:flutter/services.dart'; import 'package:flutter/services.dart';
import 'package:injectable/injectable.dart'; import 'package:injectable/injectable.dart';
//import 'package:permission_handler/permission_handler.dart'; import 'package:permission_handler/permission_handler.dart';
import 'package:revanced_manager/services/root_api.dart'; import 'package:revanced_manager/services/root_api.dart';
import 'package:revanced_manager/ui/views/home/home_view.dart'; import 'package:revanced_manager/ui/views/home/home_view.dart';
import 'package:revanced_manager/ui/views/patcher/patcher_view.dart'; import 'package:revanced_manager/ui/views/patcher/patcher_view.dart';
@@ -15,28 +15,30 @@ import 'package:stacked/stacked.dart';
class NavigationViewModel extends IndexTrackingViewModel { class NavigationViewModel extends IndexTrackingViewModel {
void initialize(BuildContext context) async { void initialize(BuildContext context) async {
SharedPreferences prefs = await SharedPreferences.getInstance(); 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) { if (prefs.getBool('useDarkTheme') == null) {
bool isDark = bool isDark =
MediaQuery.of(context).platformBrightness != Brightness.light; MediaQuery.of(context).platformBrightness != Brightness.light;
await prefs.setBool('useDarkTheme', isDark); await prefs.setBool('useDarkTheme', isDark);
await DynamicTheme.of(context)!.setTheme(isDark ? 1 : 0); await DynamicTheme.of(context)!.setTheme(isDark ? 1 : 0);
} }
SystemChrome.setEnabledSystemUIMode(SystemUiMode.edgeToEdge);
SystemChrome.setSystemUIOverlayStyle( SystemChrome.setSystemUIOverlayStyle(
SystemUiOverlayStyle( SystemUiOverlayStyle(
systemNavigationBarColor: systemNavigationBarColor: Colors.transparent,
DynamicTheme.of(context)!.theme.colorScheme.surface,
systemNavigationBarIconBrightness: systemNavigationBarIconBrightness:
DynamicTheme.of(context)!.theme.brightness == Brightness.light DynamicTheme.of(context)!.theme.brightness == Brightness.light
? Brightness.dark ? Brightness.dark
: Brightness.light, : Brightness.light,
), ),
); );
//if (prefs.getBool('permissionsRequested') == null) {
//await prefs.setBool('permissionsRequested', true);
RootAPI().hasRootPermissions();
//Permission.requestInstallPackages.request();
//Permission.ignoreBatteryOptimizations.request();
//}
} }
Widget getViewForIndex(int index) { Widget getViewForIndex(int index) {

View File

@@ -64,11 +64,11 @@ class PatcherViewModel extends BaseViewModel {
actions: <Widget>[ actions: <Widget>[
CustomMaterialButton( CustomMaterialButton(
isFilled: false, isFilled: false,
label: I18nText('cancelButton'), label: I18nText('noButton'),
onPressed: () => Navigator.of(context).pop(), onPressed: () => Navigator.of(context).pop(),
), ),
CustomMaterialButton( CustomMaterialButton(
label: I18nText('okButton'), label: I18nText('yesButton'),
onPressed: () { onPressed: () {
Navigator.of(context).pop(); Navigator.of(context).pop();
navigateToInstaller(); navigateToInstaller();

View File

@@ -145,6 +145,17 @@ class SettingsView extends StatelessWidget {
const AboutWidget(), 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 _patSourceController = TextEditingController();
final TextEditingController _orgIntSourceController = TextEditingController(); final TextEditingController _orgIntSourceController = TextEditingController();
final TextEditingController _intSourceController = TextEditingController(); final TextEditingController _intSourceController = TextEditingController();
final TextEditingController _apiUrlController = TextEditingController();
void setLanguage(String language) { void setLanguage(String language) {
notifyListeners(); notifyListeners();
@@ -55,12 +56,6 @@ class SettingsViewModel extends BaseViewModel {
} else { } else {
await DynamicTheme.of(context)!.setTheme(value ? 3 : 1); await DynamicTheme.of(context)!.setTheme(value ? 3 : 1);
} }
SystemChrome.setSystemUIOverlayStyle(
SystemUiOverlayStyle(
systemNavigationBarColor:
DynamicTheme.of(context)!.theme.colorScheme.surface,
),
);
notifyListeners(); notifyListeners();
} }
@@ -78,8 +73,6 @@ class SettingsViewModel extends BaseViewModel {
} }
SystemChrome.setSystemUIOverlayStyle( SystemChrome.setSystemUIOverlayStyle(
SystemUiOverlayStyle( SystemUiOverlayStyle(
systemNavigationBarColor:
DynamicTheme.of(context)!.theme.colorScheme.surface,
systemNavigationBarIconBrightness: systemNavigationBarIconBrightness:
value ? Brightness.light : Brightness.dark, 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 { Future<void> showResetConfirmationDialog(BuildContext context) async {
return showDialog( return showDialog(
context: context, context: context,
@@ -218,11 +270,11 @@ class SettingsViewModel extends BaseViewModel {
actions: <Widget>[ actions: <Widget>[
CustomMaterialButton( CustomMaterialButton(
isFilled: false, isFilled: false,
label: I18nText('cancelButton'), label: I18nText('noButton'),
onPressed: () => Navigator.of(context).pop(), onPressed: () => Navigator.of(context).pop(),
), ),
CustomMaterialButton( CustomMaterialButton(
label: I18nText('okButton'), label: I18nText('yesButton'),
onPressed: () { onPressed: () {
_managerAPI.setPatchesRepo(''); _managerAPI.setPatchesRepo('');
_managerAPI.setIntegrationsRepo(''); _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 { Future<int> getSdkVersion() async {
AndroidDeviceInfo info = await DeviceInfoPlugin().androidInfo; AndroidDeviceInfo info = await DeviceInfoPlugin().androidInfo;
return info.version.sdkInt ?? -1; return info.version.sdkInt ?? -1;

View File

@@ -64,8 +64,9 @@ class AppInfoView extends StatelessWidget {
CustomCard( CustomCard(
child: IntrinsicHeight( child: IntrinsicHeight(
child: Row( child: Row(
mainAxisAlignment: MainAxisAlignment.spaceEvenly, mainAxisAlignment: MainAxisAlignment.center,
children: <Widget>[ children: <Widget>[
!app.isRooted ? const Spacer() : Container(),
InkWell( InkWell(
onTap: () => model.openApp(app), onTap: () => model.openApp(app),
child: Column( child: Column(
@@ -92,11 +93,13 @@ class AppInfoView extends StatelessWidget {
], ],
), ),
), ),
const Spacer(),
VerticalDivider( VerticalDivider(
color: Theme.of(context).canvasColor, color: Theme.of(context).canvasColor,
), ),
const Spacer(),
InkWell( InkWell(
onTap: () => model.showUninstallAlertDialog( onTap: () => model.showUninstallDialog(
context, context,
app, app,
false, false,
@@ -125,9 +128,11 @@ class AppInfoView extends StatelessWidget {
], ],
), ),
), ),
const Spacer(),
VerticalDivider( VerticalDivider(
color: Theme.of(context).canvasColor, color: Theme.of(context).canvasColor,
), ),
const Spacer(),
InkWell( InkWell(
onTap: () { onTap: () {
model.navigateToPatcher(app); model.navigateToPatcher(app);
@@ -157,45 +162,49 @@ class AppInfoView extends StatelessWidget {
], ],
), ),
), ),
Visibility( app.isRooted ? const Spacer() : Container(),
visible: app.isRooted, app.isRooted
child: VerticalDivider( ? VerticalDivider(
color: Theme.of(context).canvasColor, color: Theme.of(context).canvasColor,
), )
), : Container(),
Visibility( app.isRooted ? const Spacer() : Container(),
visible: app.isRooted, app.isRooted
child: InkWell( ? InkWell(
onTap: () => model.showUninstallAlertDialog( onTap: () => model.showUninstallDialog(
context, context,
app, app,
true, true,
),
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
children: <Widget>[
Icon(
Icons.settings_backup_restore_outlined,
color:
Theme.of(context).colorScheme.primary,
), ),
const SizedBox(height: 10), child: Column(
I18nText( mainAxisAlignment:
'appInfoView.unpatchButton', MainAxisAlignment.center,
child: Text( children: <Widget>[
'', Icon(
style: TextStyle( Icons
.settings_backup_restore_outlined,
color: Theme.of(context) color: Theme.of(context)
.colorScheme .colorScheme
.primary, .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:device_apps/device_apps.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';
@@ -30,8 +31,9 @@ class AppInfoViewModel extends BaseViewModel {
} }
} }
} else { } else {
DeviceApps.uninstallApp(app.packageName); DeviceApps.uninstallApp(app.packageName).then(
_managerAPI.deletePatchedApp(app); (value) => _managerAPI.deletePatchedApp(app),
);
} }
} }
@@ -43,7 +45,7 @@ class AppInfoViewModel extends BaseViewModel {
locator<NavigationViewModel>().setIndex(1); locator<NavigationViewModel>().setIndex(1);
} }
Future<void> showUninstallAlertDialog( Future<void> showUninstallDialog(
BuildContext context, BuildContext context,
PatchedApplication app, PatchedApplication app,
bool onlyUnpatch, bool onlyUnpatch,
@@ -65,38 +67,40 @@ class AppInfoViewModel extends BaseViewModel {
), ),
); );
} else { } else {
return showDialog( if (onlyUnpatch) {
context: context, return showDialog(
builder: (context) => AlertDialog( context: context,
title: I18nText( builder: (context) => AlertDialog(
onlyUnpatch title: I18nText(
? 'appInfoView.unpatchDialogTitle' 'appInfoView.unpatchButton',
: '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(),
), ),
CustomMaterialButton( backgroundColor: Theme.of(context).colorScheme.secondaryContainer,
label: I18nText('okButton'), content: I18nText(
onPressed: () { 'appInfoView.unpatchDialogText',
uninstallApp(app, onlyUnpatch); ),
locator<HomeViewModel>().initialize(context); actions: <Widget>[
Navigator.of(context).pop(); CustomMaterialButton(
Navigator.of(context).pop(); 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

@@ -74,8 +74,8 @@ class _ApplicationItemState extends State<ApplicationItem>
crossAxisAlignment: CrossAxisAlignment.start, crossAxisAlignment: CrossAxisAlignment.start,
children: <Widget>[ children: <Widget>[
Text( Text(
widget.name.length > 10 widget.name.length > 9
? '${widget.name.substring(0, 10)}...' ? '${widget.name.substring(0, 9)}...'
: widget.name, : widget.name,
style: const TextStyle( style: const TextStyle(
fontSize: 16, fontSize: 16,

View File

@@ -4,7 +4,7 @@ homepage: https://github.com/revanced/revanced-manager
publish_to: 'none' publish_to: 'none'
version: 0.0.10+10 version: 0.0.14+14
environment: environment:
sdk: ">=2.17.5 <3.0.0" sdk: ">=2.17.5 <3.0.0"
@@ -52,7 +52,7 @@ dependencies:
ref: feature/nullSafe ref: feature/nullSafe
package_info_plus: ^1.4.3+1 package_info_plus: ^1.4.3+1
path_provider: ^2.0.11 path_provider: ^2.0.11
#permission_handler: ^10.0.0 permission_handler: ^10.0.0
pull_to_refresh: ^2.0.0 pull_to_refresh: ^2.0.0
root: ^2.0.2 root: ^2.0.2
share_extend: ^2.0.0 share_extend: ^2.0.0