diff --git a/.github/workflows/release-build.yml b/.github/workflows/release-build.yml
index 201a79ff..261a84bb 100644
--- a/.github/workflows/release-build.yml
+++ b/.github/workflows/release-build.yml
@@ -19,6 +19,8 @@ jobs:
channel: 'stable'
- name: Set up Flutter
run: flutter pub get
+ - name: Generate files with Builder
+ run: flutter packages pub run build_runner build --delete-conflicting-outputs
- name: Build with Flutter
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
diff --git a/.gitignore b/.gitignore
index ab6de429..a78395b8 100644
--- a/.gitignore
+++ b/.gitignore
@@ -42,6 +42,8 @@ version
# Flutter/Dart/Pub related
**/doc/api/
**/*.g.dart
+**/*.locator.dart
+**/*.router.dart
.dart_tool/
.flutter-plugins
.flutter-plugins-dependencies
diff --git a/.vscode/tasks.json b/.vscode/tasks.json
index 490ed99c..0fa4713f 100644
--- a/.vscode/tasks.json
+++ b/.vscode/tasks.json
@@ -2,7 +2,7 @@
"version": "2.0.0",
"tasks": [
{
- "label": "Build (Serializer)",
+ "label": "Generate (Builder)",
"type": "shell",
"command": "flutter packages pub run build_runner build --delete-conflicting-outputs",
"problemMatcher": []
@@ -30,7 +30,7 @@
"problemMatcher": []
},
{
- "label": "Clean (Serializer)",
+ "label": "Clean (Builder)",
"type": "shell",
"command": "flutter packages pub run build_runner clean",
"problemMatcher": []
@@ -39,7 +39,7 @@
"label": "Build all (Android)",
"dependsOrder": "sequence",
"dependsOn": [
- "Build (Serializer)",
+ "Generate (Builder)",
"Build (Android)"
],
"problemMatcher": []
@@ -49,7 +49,7 @@
"dependsOrder": "sequence",
"dependsOn": [
"Clean (Flutter)",
- "Clean (Serializer)"
+ "Clean (Builder)"
],
"problemMatcher": []
},
diff --git a/android/app/src/main/kotlin/app/revanced/manager/MainActivity.kt b/android/app/src/main/kotlin/app/revanced/manager/MainActivity.kt
index 971836df..ba725783 100644
--- a/android/app/src/main/kotlin/app/revanced/manager/MainActivity.kt
+++ b/android/app/src/main/kotlin/app/revanced/manager/MainActivity.kt
@@ -179,10 +179,41 @@ class MainActivity : FlutterActivity() {
return true
}
- fun createPatcher(inputFilePath: String, cacheDirPath: String, resourcePatching: Boolean): Boolean {
+ fun createPatcher(
+ inputFilePath: String,
+ cacheDirPath: String,
+ resourcePatching: Boolean
+ ): Boolean {
val inputFile = File(inputFilePath)
val aaptPath = Aapt.binary(applicationContext).absolutePath
- patcher = Patcher(PatcherOptions(inputFile, cacheDirPath, resourcePatching, aaptPath, cacheDirPath))
+ patcher =
+ Patcher(
+ PatcherOptions(
+ inputFile,
+ cacheDirPath,
+ resourcePatching,
+ aaptPath,
+ cacheDirPath,
+ logger =
+ object : app.revanced.patcher.logging.Logger {
+ override fun error(msg: String) {
+ methodChannel.invokeMethod("updateInstallerLog", msg)
+ }
+
+ override fun warn(msg: String) {
+ methodChannel.invokeMethod("updateInstallerLog", msg)
+ }
+
+ override fun info(msg: String) {
+ methodChannel.invokeMethod("updateInstallerLog", msg)
+ }
+
+ override fun trace(msg: String) {
+ methodChannel.invokeMethod("updateInstallerLog", msg)
+ }
+ }
+ )
+ )
return true
}
diff --git a/android/app/src/main/res/drawable/ic_notification.xml b/android/app/src/main/res/drawable/ic_notification.xml
new file mode 100644
index 00000000..17e031d9
--- /dev/null
+++ b/android/app/src/main/res/drawable/ic_notification.xml
@@ -0,0 +1,33 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/assets/i18n/en.json b/assets/i18n/en.json
index 795c7db0..638a6fd6 100644
--- a/assets/i18n/en.json
+++ b/assets/i18n/en.json
@@ -7,18 +7,12 @@
"homeView": {
"widgetTitle": "Dashboard",
"updatesSubtitle": "ReVanced Updates",
- "patchedSubtitle": "Patched Applications"
- },
- "availableUpdatesCard": {
- "widgetTitle": "Updates Available",
- "patchButton": "Patch All",
- "changelogLabel": "Changelog"
+ "patchedSubtitle": "Patched Applications",
+ "updatesAvailable": "Updates Available",
+ "installed": "Installed"
},
"applicationItem": {
- "patchButton": "Patch"
- },
- "installedAppsCard": {
- "widgetTitle": "Total Installed",
+ "patchButton": "Patch",
"changelogLabel": "Changelog"
},
"latestCommitCard": {
diff --git a/assets/images/reddit.png b/assets/images/reddit.png
deleted file mode 100644
index 941a12e6..00000000
Binary files a/assets/images/reddit.png and /dev/null differ
diff --git a/assets/images/revanced.svg b/assets/images/revanced.svg
deleted file mode 100644
index 7318abbd..00000000
--- a/assets/images/revanced.svg
+++ /dev/null
@@ -1,6 +0,0 @@
-
diff --git a/lib/app/app.dart b/lib/app/app.dart
index 6d7ab8ec..8765ae4f 100644
--- a/lib/app/app.dart
+++ b/lib/app/app.dart
@@ -5,6 +5,7 @@ import 'package:revanced_manager/services/root_api.dart';
import 'package:revanced_manager/ui/views/app_selector/app_selector_view.dart';
import 'package:revanced_manager/ui/views/app_selector/app_selector_viewmodel.dart';
import 'package:revanced_manager/ui/views/contributors/contributors_view.dart';
+import 'package:revanced_manager/ui/views/home/home_viewmodel.dart';
import 'package:revanced_manager/ui/views/installer/installer_view.dart';
import 'package:revanced_manager/ui/views/installer/installer_viewmodel.dart';
import 'package:revanced_manager/ui/views/patcher/patcher_viewmodel.dart';
@@ -31,6 +32,7 @@ import 'package:stacked_themes/stacked_themes.dart';
LazySingleton(classType: PatcherAPI),
LazySingleton(classType: ManagerAPI),
LazySingleton(classType: RootAPI),
+ LazySingleton(classType: HomeViewModel),
LazySingleton(classType: PatcherViewModel),
LazySingleton(classType: AppSelectorViewModel),
LazySingleton(classType: PatchesSelectorViewModel),
diff --git a/lib/app/app.locator.dart b/lib/app/app.locator.dart
deleted file mode 100644
index 6476d9a5..00000000
--- a/lib/app/app.locator.dart
+++ /dev/null
@@ -1,39 +0,0 @@
-// GENERATED CODE - DO NOT MODIFY BY HAND
-
-// **************************************************************************
-// StackedLocatorGenerator
-// **************************************************************************
-
-// ignore_for_file: public_member_api_docs, depend_on_referenced_packages
-
-import 'package:stacked_core/stacked_core.dart';
-import 'package:stacked_services/stacked_services.dart';
-import 'package:stacked_themes/stacked_themes.dart';
-
-import '../services/manager_api.dart';
-import '../services/patcher_api.dart';
-import '../services/root_api.dart';
-import '../ui/views/app_selector/app_selector_viewmodel.dart';
-import '../ui/views/installer/installer_viewmodel.dart';
-import '../ui/views/patcher/patcher_viewmodel.dart';
-import '../ui/views/patches_selector/patches_selector_viewmodel.dart';
-
-final locator = StackedLocator.instance;
-
-Future setupLocator(
- {String? environment, EnvironmentFilter? environmentFilter}) async {
-// Register environments
- locator.registerEnvironment(
- environment: environment, environmentFilter: environmentFilter);
-
-// Register dependencies
- locator.registerLazySingleton(() => NavigationService());
- locator.registerLazySingleton(() => PatcherAPI());
- locator.registerLazySingleton(() => ManagerAPI());
- locator.registerLazySingleton(() => RootAPI());
- locator.registerLazySingleton(() => PatcherViewModel());
- locator.registerLazySingleton(() => AppSelectorViewModel());
- locator.registerLazySingleton(() => PatchesSelectorViewModel());
- locator.registerLazySingleton(() => InstallerViewModel());
- locator.registerLazySingleton(() => ThemeService.getInstance());
-}
diff --git a/lib/app/app.router.dart b/lib/app/app.router.dart
deleted file mode 100644
index 62e381df..00000000
--- a/lib/app/app.router.dart
+++ /dev/null
@@ -1,231 +0,0 @@
-// GENERATED CODE - DO NOT MODIFY BY HAND
-
-// **************************************************************************
-// StackedRouterGenerator
-// **************************************************************************
-
-// ignore_for_file: public_member_api_docs, unused_import, non_constant_identifier_names
-
-import 'package:flutter/material.dart';
-import 'package:stacked/stacked.dart';
-import 'package:stacked_services/stacked_services.dart';
-
-import '../main.dart';
-import '../ui/views/app_selector/app_selector_view.dart';
-import '../ui/views/contributors/contributors_view.dart';
-import '../ui/views/installer/installer_view.dart';
-import '../ui/views/patches_selector/patches_selector_view.dart';
-import '../ui/views/root_checker/root_checker_view.dart';
-import '../ui/views/settings/settings_view.dart';
-
-class Routes {
- static const String navigation = '/Navigation';
- static const String appSelectorView = '/app-selector-view';
- static const String patchesSelectorView = '/patches-selector-view';
- static const String installerView = '/installer-view';
- static const String settingsView = '/settings-view';
- static const String contributorsView = '/contributors-view';
- static const String rootCheckerView = '/root-checker-view';
- static const all = {
- navigation,
- appSelectorView,
- patchesSelectorView,
- installerView,
- settingsView,
- contributorsView,
- rootCheckerView,
- };
-}
-
-class StackedRouter extends RouterBase {
- @override
- List get routes => _routes;
- final _routes = [
- RouteDef(Routes.navigation, page: Navigation),
- RouteDef(Routes.appSelectorView, page: AppSelectorView),
- RouteDef(Routes.patchesSelectorView, page: PatchesSelectorView),
- RouteDef(Routes.installerView, page: InstallerView),
- RouteDef(Routes.settingsView, page: SettingsView),
- RouteDef(Routes.contributorsView, page: ContributorsView),
- RouteDef(Routes.rootCheckerView, page: RootCheckerView),
- ];
- @override
- Map get pagesMap => _pagesMap;
- final _pagesMap = {
- Navigation: (data) {
- return MaterialPageRoute(
- builder: (context) => const Navigation(),
- settings: data,
- );
- },
- AppSelectorView: (data) {
- return MaterialPageRoute(
- builder: (context) => const AppSelectorView(),
- settings: data,
- );
- },
- PatchesSelectorView: (data) {
- return MaterialPageRoute(
- builder: (context) => const PatchesSelectorView(),
- settings: data,
- );
- },
- InstallerView: (data) {
- var args = data.getArgs(
- orElse: () => InstallerViewArguments(),
- );
- return MaterialPageRoute(
- builder: (context) => InstallerView(key: args.key),
- settings: data,
- );
- },
- SettingsView: (data) {
- return MaterialPageRoute(
- builder: (context) => const SettingsView(),
- settings: data,
- );
- },
- ContributorsView: (data) {
- return MaterialPageRoute(
- builder: (context) => const ContributorsView(),
- settings: data,
- );
- },
- RootCheckerView: (data) {
- return MaterialPageRoute(
- builder: (context) => const RootCheckerView(),
- settings: data,
- );
- },
- };
-}
-
-/// ************************************************************************
-/// Arguments holder classes
-/// *************************************************************************
-
-/// InstallerView arguments holder class
-class InstallerViewArguments {
- final Key? key;
- InstallerViewArguments({this.key});
-}
-
-/// ************************************************************************
-/// Extension for strongly typed navigation
-/// *************************************************************************
-
-extension NavigatorStateExtension on NavigationService {
- Future navigateToNavigation({
- int? routerId,
- bool preventDuplicates = true,
- Map? parameters,
- Widget Function(BuildContext, Animation, Animation, Widget)?
- transition,
- }) async {
- return navigateTo(
- Routes.navigation,
- id: routerId,
- preventDuplicates: preventDuplicates,
- parameters: parameters,
- transition: transition,
- );
- }
-
- Future navigateToAppSelectorView({
- int? routerId,
- bool preventDuplicates = true,
- Map? parameters,
- Widget Function(BuildContext, Animation, Animation, Widget)?
- transition,
- }) async {
- return navigateTo(
- Routes.appSelectorView,
- id: routerId,
- preventDuplicates: preventDuplicates,
- parameters: parameters,
- transition: transition,
- );
- }
-
- Future navigateToPatchesSelectorView({
- int? routerId,
- bool preventDuplicates = true,
- Map? parameters,
- Widget Function(BuildContext, Animation, Animation, Widget)?
- transition,
- }) async {
- return navigateTo(
- Routes.patchesSelectorView,
- id: routerId,
- preventDuplicates: preventDuplicates,
- parameters: parameters,
- transition: transition,
- );
- }
-
- Future navigateToInstallerView({
- Key? key,
- int? routerId,
- bool preventDuplicates = true,
- Map? parameters,
- Widget Function(BuildContext, Animation, Animation, Widget)?
- transition,
- }) async {
- return navigateTo(
- Routes.installerView,
- arguments: InstallerViewArguments(key: key),
- id: routerId,
- preventDuplicates: preventDuplicates,
- parameters: parameters,
- transition: transition,
- );
- }
-
- Future navigateToSettingsView({
- int? routerId,
- bool preventDuplicates = true,
- Map? parameters,
- Widget Function(BuildContext, Animation, Animation, Widget)?
- transition,
- }) async {
- return navigateTo(
- Routes.settingsView,
- id: routerId,
- preventDuplicates: preventDuplicates,
- parameters: parameters,
- transition: transition,
- );
- }
-
- Future navigateToContributorsView({
- int? routerId,
- bool preventDuplicates = true,
- Map? parameters,
- Widget Function(BuildContext, Animation, Animation, Widget)?
- transition,
- }) async {
- return navigateTo(
- Routes.contributorsView,
- id: routerId,
- preventDuplicates: preventDuplicates,
- parameters: parameters,
- transition: transition,
- );
- }
-
- Future navigateToRootCheckerView({
- int? routerId,
- bool preventDuplicates = true,
- Map? parameters,
- Widget Function(BuildContext, Animation, Animation, Widget)?
- transition,
- }) async {
- return navigateTo(
- Routes.rootCheckerView,
- id: routerId,
- preventDuplicates: preventDuplicates,
- parameters: parameters,
- transition: transition,
- );
- }
-}
diff --git a/lib/models/patched_application.dart b/lib/models/patched_application.dart
index ecb7a62c..ea530fa6 100644
--- a/lib/models/patched_application.dart
+++ b/lib/models/patched_application.dart
@@ -1,21 +1,43 @@
-import 'package:revanced_manager/models/patch.dart';
+import 'dart:typed_data';
+import 'package:json_annotation/json_annotation.dart';
+part 'patched_application.g.dart';
+
+@JsonSerializable()
class PatchedApplication {
final String name;
final String packageName;
final String version;
final String apkFilePath;
+ @JsonKey(
+ fromJson: bytesFromString,
+ toJson: bytesToString,
+ )
+ final Uint8List icon;
+ final DateTime patchDate;
final bool isRooted;
final bool isFromStorage;
- final List appliedPatches;
+ final List appliedPatches;
PatchedApplication({
required this.name,
required this.packageName,
required this.version,
required this.apkFilePath,
+ required this.icon,
+ required this.patchDate,
required this.isRooted,
required this.isFromStorage,
- this.appliedPatches = const [],
+ this.appliedPatches = const [],
});
+
+ factory PatchedApplication.fromJson(Map json) =>
+ _$PatchedApplicationFromJson(json);
+
+ Map toJson() => _$PatchedApplicationToJson(this);
+
+ static Uint8List bytesFromString(String pictureUrl) =>
+ Uint8List.fromList(pictureUrl.codeUnits);
+
+ static String bytesToString(Uint8List bytes) => String.fromCharCodes(bytes);
}
diff --git a/lib/theme.dart b/lib/theme.dart
index 75eda588..354276c7 100644
--- a/lib/theme.dart
+++ b/lib/theme.dart
@@ -24,7 +24,7 @@ var lightTheme = ThemeData.light().copyWith(
padding: MaterialStateProperty.all(
const EdgeInsets.symmetric(
vertical: 8,
- horizontal: 12,
+ horizontal: 14,
),
),
backgroundColor: MaterialStateProperty.all(
@@ -70,7 +70,7 @@ var darkTheme = ThemeData.dark().copyWith(
),
),
colorScheme: const ColorScheme.dark(
- primary: Color(0x1B222B6B),
+ primary: Color(0xff11161C),
secondary: Color(0xff7792BA),
tertiary: Color(0xff8691A0),
background: Color(0xff0A0D11),
diff --git a/lib/ui/views/app_selector/app_selector_viewmodel.dart b/lib/ui/views/app_selector/app_selector_viewmodel.dart
index a3541144..27cf1c4a 100644
--- a/lib/ui/views/app_selector/app_selector_viewmodel.dart
+++ b/lib/ui/views/app_selector/app_selector_viewmodel.dart
@@ -4,7 +4,6 @@ import 'package:file_picker/file_picker.dart';
import 'package:flutter/cupertino.dart';
import 'package:flutter_i18n/flutter_i18n.dart';
import 'package:fluttertoast/fluttertoast.dart';
-import 'package:package_archive_info/package_archive_info.dart';
import 'package:revanced_manager/app/app.locator.dart';
import 'package:revanced_manager/models/patched_application.dart';
import 'package:revanced_manager/services/patcher_api.dart';
@@ -39,6 +38,8 @@ class AppSelectorViewModel extends BaseViewModel {
packageName: application.packageName,
version: application.versionName!,
apkFilePath: application.apkFilePath,
+ icon: application.icon,
+ patchDate: DateTime.now(),
isRooted: isRooted,
isFromStorage: isFromStorage,
);
@@ -57,20 +58,25 @@ class AppSelectorViewModel extends BaseViewModel {
);
if (result != null && result.files.single.path != null) {
File apkFile = File(result.files.single.path!);
- PackageArchiveInfo? packageArchiveInfo =
- await PackageArchiveInfo.fromPath(apkFile.path);
- PatchedApplication app = PatchedApplication(
- name: packageArchiveInfo.appName,
- packageName: packageArchiveInfo.packageName,
- version: packageArchiveInfo.version,
- apkFilePath: result.files.single.path!,
- isRooted: isRooted,
- isFromStorage: isFromStorage,
- );
- locator().selectedApp = app;
- locator().selectedPatches.clear();
- locator().dimPatchCard = false;
- locator().notifyListeners();
+ ApplicationWithIcon? application =
+ await DeviceApps.getAppFromStorage(apkFile.path, true)
+ as ApplicationWithIcon?;
+ if (application != null) {
+ PatchedApplication app = PatchedApplication(
+ name: application.appName,
+ packageName: application.packageName,
+ version: application.versionName!,
+ apkFilePath: result.files.single.path!,
+ icon: application.icon,
+ patchDate: DateTime.now(),
+ isRooted: isRooted,
+ isFromStorage: isFromStorage,
+ );
+ locator().selectedApp = app;
+ locator().selectedPatches.clear();
+ locator().dimPatchCard = false;
+ locator().notifyListeners();
+ }
}
} on Exception {
Fluttertoast.showToast(
diff --git a/lib/ui/views/home/home_view.dart b/lib/ui/views/home/home_view.dart
index 551f0c48..2c27937b 100644
--- a/lib/ui/views/home/home_view.dart
+++ b/lib/ui/views/home/home_view.dart
@@ -1,9 +1,11 @@
import 'package:flutter/material.dart';
import 'package:flutter_i18n/flutter_i18n.dart';
import 'package:google_fonts/google_fonts.dart';
+import 'package:revanced_manager/app/app.locator.dart';
import 'package:revanced_manager/theme.dart';
import 'package:revanced_manager/ui/views/home/home_viewmodel.dart';
import 'package:revanced_manager/ui/widgets/available_updates_card.dart';
+import 'package:revanced_manager/ui/widgets/dashboard_raw_chip.dart';
import 'package:revanced_manager/ui/widgets/installed_apps_card.dart';
import 'package:revanced_manager/ui/widgets/latest_commit_card.dart';
import 'package:stacked/stacked.dart';
@@ -13,8 +15,9 @@ class HomeView extends StatelessWidget {
@override
Widget build(BuildContext context) {
- return ViewModelBuilder.reactive(
- viewModelBuilder: () => HomeViewModel(),
+ return ViewModelBuilder.reactive(
+ disposeViewModel: false,
+ viewModelBuilder: () => locator(),
builder: (context, model, child) => Scaffold(
body: SafeArea(
child: SingleChildScrollView(
@@ -30,6 +33,7 @@ class HomeView extends StatelessWidget {
'',
style: GoogleFonts.inter(
fontSize: 28,
+ fontWeight: FontWeight.w600,
),
),
),
@@ -40,6 +44,7 @@ class HomeView extends StatelessWidget {
'',
style: GoogleFonts.inter(
fontSize: 20,
+ fontWeight: FontWeight.w500,
color: isDark
? const Color(0xffD1E1FA)
: const Color(0xff384E6E),
@@ -48,7 +53,8 @@ class HomeView extends StatelessWidget {
),
const SizedBox(height: 10),
LatestCommitCard(
- color: Theme.of(context).colorScheme.primary),
+ color: Theme.of(context).colorScheme.primary,
+ ),
const SizedBox(height: 14),
I18nText(
'homeView.patchedSubtitle',
@@ -62,14 +68,30 @@ class HomeView extends StatelessWidget {
),
),
),
+ const SizedBox(height: 8),
+ Row(
+ children: [
+ DashboardChip(
+ label: "homeView.updatesAvailable",
+ isSelected: model.showUpdatableApps,
+ onSelected: (value) {
+ model.toggleUpdatableApps(value);
+ },
+ ),
+ const SizedBox(width: 10),
+ DashboardChip(
+ label: "homeView.installed",
+ isSelected: !model.showUpdatableApps,
+ onSelected: (value) {
+ model.toggleUpdatableApps(false);
+ },
+ )
+ ],
+ ),
const SizedBox(height: 14),
- AvailableUpdatesCard(
- color: Theme.of(context).colorScheme.primary,
- ),
- const SizedBox(height: 15),
- InstalledAppsCard(
- color: Theme.of(context).colorScheme.primary,
- ),
+ model.showUpdatableApps
+ ? const AvailableUpdatesCard()
+ : const InstalledAppsCard()
],
),
),
diff --git a/lib/ui/views/home/home_viewmodel.dart b/lib/ui/views/home/home_viewmodel.dart
index 79882510..e9929eed 100644
--- a/lib/ui/views/home/home_viewmodel.dart
+++ b/lib/ui/views/home/home_viewmodel.dart
@@ -1,8 +1,28 @@
+import 'dart:convert';
+import 'package:injectable/injectable.dart';
import 'package:revanced_manager/app/app.locator.dart';
+import 'package:revanced_manager/models/patched_application.dart';
import 'package:revanced_manager/services/manager_api.dart';
+import 'package:shared_preferences/shared_preferences.dart';
import 'package:stacked/stacked.dart';
+@lazySingleton
class HomeViewModel extends BaseViewModel {
+ bool showUpdatableApps = true;
+
+ void toggleUpdatableApps(bool value) {
+ showUpdatableApps = value;
+ notifyListeners();
+ }
+
Future downloadPatches() => locator().downloadPatches();
Future downloadIntegrations() => locator().downloadIntegrations();
+
+ Future> getPatchedApps(bool isUpdatable) async {
+ SharedPreferences prefs = await SharedPreferences.getInstance();
+ List patchedApps = prefs.getStringList('patchedApps') ?? [];
+ return patchedApps
+ .map((app) => PatchedApplication.fromJson(json.decode(app)))
+ .toList();
+ }
}
diff --git a/lib/ui/views/installer/installer_view.dart b/lib/ui/views/installer/installer_view.dart
index f915a568..ef359925 100644
--- a/lib/ui/views/installer/installer_view.dart
+++ b/lib/ui/views/installer/installer_view.dart
@@ -1,5 +1,6 @@
import 'package:flutter/material.dart';
import 'package:flutter_i18n/flutter_i18n.dart';
+import 'package:google_fonts/google_fonts.dart';
import 'package:revanced_manager/app/app.locator.dart';
import 'package:revanced_manager/ui/views/installer/installer_viewmodel.dart';
import 'package:stacked/stacked.dart';
@@ -81,9 +82,8 @@ class InstallerView extends StatelessWidget {
),
child: SelectableText(
model.logs,
- style: const TextStyle(
- fontFamily: 'monospace',
- fontSize: 15,
+ style: GoogleFonts.jetBrainsMono(
+ fontSize: 12,
height: 1.5,
),
),
diff --git a/lib/ui/views/installer/installer_viewmodel.dart b/lib/ui/views/installer/installer_viewmodel.dart
index 708c3ff4..557a898c 100644
--- a/lib/ui/views/installer/installer_viewmodel.dart
+++ b/lib/ui/views/installer/installer_viewmodel.dart
@@ -7,6 +7,7 @@ import 'package:revanced_manager/services/patcher_api.dart';
import 'package:revanced_manager/ui/views/app_selector/app_selector_viewmodel.dart';
import 'package:revanced_manager/ui/views/patcher/patcher_viewmodel.dart';
import 'package:revanced_manager/ui/views/patches_selector/patches_selector_viewmodel.dart';
+import 'package:shared_preferences/shared_preferences.dart';
import 'package:stacked/stacked.dart';
class InstallerViewModel extends BaseViewModel {
@@ -23,7 +24,7 @@ class InstallerViewModel extends BaseViewModel {
notificationText: 'ReVanced Manager is patching',
notificationImportance: AndroidNotificationImportance.Default,
notificationIcon: AndroidResource(
- name: 'ic_launcher_foreground',
+ name: 'ic_notification',
defType: 'drawable',
),
),
@@ -34,11 +35,13 @@ class InstallerViewModel extends BaseViewModel {
}
void addLog(String message) {
- if (logs.isNotEmpty) {
- logs += '\n';
+ if (message.isNotEmpty && !message.startsWith('Merging L')) {
+ if (logs.isNotEmpty) {
+ logs += '\n';
+ }
+ logs += message;
+ notifyListeners();
}
- logs += message;
- notifyListeners();
}
void updateProgress(double value) {
@@ -61,29 +64,25 @@ class InstallerViewModel extends BaseViewModel {
List selectedPatches =
locator().selectedPatches;
if (selectedPatches.isNotEmpty) {
- addLog('Initializing installer...');
+ addLog('Initializing installer');
if (selectedApp.isRooted && !selectedApp.isFromStorage) {
- addLog('Checking if an old patched version exists...');
+ addLog('Checking if an old patched version exists');
bool oldExists =
await locator().checkOldPatch(selectedApp);
- addLog('Done');
if (oldExists) {
- addLog('Deleting old patched version...');
+ addLog('Deleting old patched version');
await locator().deleteOldPatch(selectedApp);
- addLog('Done');
}
}
- addLog('Creating working directory...');
+ addLog('Creating working directory');
bool? isSuccess = await locator().initPatcher();
if (isSuccess != null && isSuccess) {
- addLog('Done');
updateProgress(0.1);
- addLog('Copying original apk...');
+ addLog('Copying original apk');
isSuccess = await locator().copyInputFile(apkFilePath);
if (isSuccess != null && isSuccess) {
- addLog('Done');
updateProgress(0.2);
- addLog('Creating patcher...');
+ addLog('Creating patcher');
bool resourcePatching = false;
if (selectedApp.packageName == 'com.google.android.youtube' ||
selectedApp.packageName ==
@@ -95,31 +94,26 @@ class InstallerViewModel extends BaseViewModel {
);
if (isSuccess != null && isSuccess) {
if (selectedApp.packageName == 'com.google.android.youtube') {
- addLog('Done');
updateProgress(0.3);
- addLog('Merging integrations...');
+ addLog('Merging integrations');
isSuccess = await locator().mergeIntegrations();
}
if (isSuccess != null && isSuccess) {
- addLog('Done');
updateProgress(0.5);
- addLog('Applying patches...');
isSuccess =
await locator().applyPatches(selectedPatches);
if (isSuccess != null && isSuccess) {
- addLog('Done');
updateProgress(0.7);
- addLog('Repacking patched apk...');
+ addLog('Repacking patched apk');
isSuccess = await locator().repackPatchedFile();
if (isSuccess != null && isSuccess) {
- addLog('Done');
updateProgress(0.9);
- addLog('Signing patched apk...');
+ addLog('Signing patched apk');
isSuccess = await locator().signPatchedFile();
if (isSuccess != null && isSuccess) {
- addLog('Done');
showButtons = true;
updateProgress(1.0);
+ addLog('Finished');
}
}
}
@@ -128,13 +122,13 @@ class InstallerViewModel extends BaseViewModel {
}
}
if (isSuccess == null || !isSuccess) {
- addLog('An error occurred! Aborting...');
+ addLog('An error occurred! Aborting');
}
} else {
- addLog('No patches selected! Aborting...');
+ addLog('No patches selected! Aborting');
}
} else {
- addLog('No app selected! Aborting...');
+ addLog('No app selected! Aborting');
}
await FlutterBackground.disableBackgroundExecution();
isPatching = false;
@@ -145,13 +139,14 @@ class InstallerViewModel extends BaseViewModel {
locator().selectedApp;
if (selectedApp != null) {
addLog(selectedApp.isRooted
- ? 'Installing patched file using root method...'
- : 'Installing patched file using nonroot method...');
+ ? 'Installing patched file using root method'
+ : 'Installing patched file using nonroot method');
isInstalled = await locator().installPatchedFile(selectedApp);
if (isInstalled) {
addLog('Done');
+ await saveApp(selectedApp);
} else {
- addLog('An error occurred! Aborting...');
+ addLog('An error occurred! Aborting');
}
}
}
@@ -181,4 +176,14 @@ class InstallerViewModel extends BaseViewModel {
DeviceApps.openApp(selectedApp.packageName);
}
}
+
+ Future saveApp(PatchedApplication selectedApp) async {
+ SharedPreferences prefs = await SharedPreferences.getInstance();
+ List patchedApps = prefs.getStringList('patchedApps') ?? [];
+ String app = selectedApp.toJson().toString();
+ if (!patchedApps.contains(app)) {
+ patchedApps.add(app);
+ prefs.setStringList('patchedApps', patchedApps);
+ }
+ }
}
diff --git a/lib/ui/widgets/application_item.dart b/lib/ui/widgets/application_item.dart
index 805e8a24..d9f462ad 100644
--- a/lib/ui/widgets/application_item.dart
+++ b/lib/ui/widgets/application_item.dart
@@ -1,57 +1,114 @@
+import 'dart:typed_data';
import 'package:flutter/material.dart';
import 'package:flutter_i18n/flutter_i18n.dart';
-import 'package:flutter_svg/flutter_svg.dart';
import 'package:google_fonts/google_fonts.dart';
import 'package:revanced_manager/constants.dart';
+import 'package:revanced_manager/theme.dart';
import 'package:revanced_manager/ui/widgets/patch_text_button.dart';
+import 'package:expandable/expandable.dart';
+import 'package:timeago/timeago.dart';
class ApplicationItem extends StatelessWidget {
- final String asset;
+ final Uint8List icon;
final String name;
- final String releaseDate;
+ final DateTime patchDate;
+ final String? changelog;
+ final bool isUpdatableApp;
final Function()? onPressed;
const ApplicationItem({
Key? key,
- required this.asset,
+ required this.icon,
required this.name,
- required this.releaseDate,
+ required this.patchDate,
+ this.changelog = '',
+ required this.isUpdatableApp,
required this.onPressed,
}) : super(key: key);
@override
Widget build(BuildContext context) {
- final isSVG = asset.endsWith('.svg');
- return ListTile(
- horizontalTitleGap: 12.0,
- leading: isSVG
- ? SvgPicture.asset(
- asset,
- height: 26,
- width: 26,
- )
- : Image.asset(
- asset,
- height: 39,
- width: 39,
+ return ExpandablePanel(
+ theme: const ExpandableThemeData(
+ hasIcon: false,
+ animationDuration: Duration(milliseconds: 450),
+ ),
+ header: Container(
+ height: 60,
+ decoration: BoxDecoration(
+ borderRadius: const BorderRadius.all(
+ Radius.circular(16),
+ ),
+ color: Theme.of(context).colorScheme.primary,
+ ),
+ padding: const EdgeInsets.symmetric(horizontal: 10.0, vertical: 12.0),
+ child: Row(
+ children: [
+ SizedBox(
+ width: 60,
+ child: Image.memory(
+ icon,
+ height: 39,
+ width: 39,
+ ),
),
- title: Text(
- name,
- style: GoogleFonts.roboto(
- color: Theme.of(context).colorScheme.secondary,
- fontWeight: FontWeight.w600,
+ const SizedBox(width: 4),
+ SizedBox(
+ width: MediaQuery.of(context).size.width - 250,
+ child: Column(
+ crossAxisAlignment: CrossAxisAlignment.start,
+ children: [
+ Text(
+ name,
+ style: GoogleFonts.roboto(
+ color: Theme.of(context).colorScheme.secondary,
+ fontWeight: FontWeight.w600,
+ ),
+ ),
+ Text(
+ format(patchDate),
+ style: robotoTextStyle.copyWith(
+ color: Theme.of(context).colorScheme.tertiary,
+ ),
+ ),
+ ],
+ ),
+ ),
+ const Spacer(),
+ isUpdatableApp
+ ? Padding(
+ padding: const EdgeInsets.symmetric(horizontal: 8.0),
+ child: PatchTextButton(
+ text: 'applicationItem.patchButton',
+ onPressed: onPressed,
+ borderColor: isDark
+ ? const Color(0xff4D5054)
+ : const Color.fromRGBO(119, 146, 168, 1),
+ ),
+ )
+ : const SizedBox(),
+ ],
),
),
- subtitle: Text(
- releaseDate,
- style: robotoTextStyle,
- ),
- trailing: PatchTextButton(
- text: FlutterI18n.translate(
- context,
- 'applicationItem.patchButton',
+ collapsed: const Text(""),
+ expanded: Padding(
+ padding: const EdgeInsets.symmetric(vertical: 8.0, horizontal: 16.0),
+ child: Column(
+ crossAxisAlignment: CrossAxisAlignment.start,
+ children: [
+ I18nText(
+ 'applicationItem.changelogLabel',
+ child: Text(
+ '',
+ style: robotoTextStyle.copyWith(fontWeight: FontWeight.w700),
+ ),
+ ),
+ Text(
+ changelog!,
+ style: robotoTextStyle,
+ ),
+ ],
),
- onPressed: onPressed,
),
);
}
diff --git a/lib/ui/widgets/available_updates_card.dart b/lib/ui/widgets/available_updates_card.dart
index d2e05b7f..00a7e0c0 100644
--- a/lib/ui/widgets/available_updates_card.dart
+++ b/lib/ui/widgets/available_updates_card.dart
@@ -1,94 +1,36 @@
import 'package:flutter/material.dart';
-import 'package:flutter_i18n/flutter_i18n.dart';
-import 'package:google_fonts/google_fonts.dart';
+import 'package:revanced_manager/app/app.locator.dart';
+import 'package:revanced_manager/models/patched_application.dart';
+import 'package:revanced_manager/ui/views/home/home_viewmodel.dart';
import 'package:revanced_manager/ui/widgets/application_item.dart';
-import 'package:revanced_manager/ui/widgets/patch_text_button.dart';
class AvailableUpdatesCard extends StatelessWidget {
- final Color? color;
const AvailableUpdatesCard({
Key? key,
- this.color = const Color(0xff1B222B),
}) : super(key: key);
@override
Widget build(BuildContext context) {
- return Container(
- decoration: BoxDecoration(
- borderRadius: BorderRadius.circular(12),
- color: color,
- ),
- padding: const EdgeInsets.symmetric(vertical: 18, horizontal: 12),
- child: Column(
- crossAxisAlignment: CrossAxisAlignment.start,
- mainAxisAlignment: MainAxisAlignment.start,
- children: [
- Padding(
- padding: const EdgeInsets.symmetric(horizontal: 12.0),
- child: Row(
- mainAxisAlignment: MainAxisAlignment.spaceBetween,
- children: [
- I18nText(
- 'availableUpdatesCard.widgetTitle',
- child: Text(
- '',
- style: GoogleFonts.inter(
- fontSize: 16,
- color: Theme.of(context).colorScheme.secondary,
- fontWeight: FontWeight.w500,
- ),
- ),
- ),
- PatchTextButton(
- text: FlutterI18n.translate(
- context,
- 'availableUpdatesCard.patchButton',
- ),
- onPressed: () => {},
- backgroundColor: Theme.of(context).colorScheme.secondary,
- ),
- ],
- ),
- ),
- ApplicationItem(
- asset: 'assets/images/revanced.svg',
- name: 'ReVanced',
- releaseDate: '2 days ago',
- onPressed: () => {},
- ),
- ApplicationItem(
- asset: 'assets/images/reddit.png',
- name: 'ReReddit',
- releaseDate: 'Released 1 month ago',
- onPressed: () => {},
- ),
- const SizedBox(height: 4),
- I18nText(
- 'availableUpdatesCard.changelogLabel',
- child: Text(
- '',
- style: GoogleFonts.roboto(
- color: Theme.of(context).colorScheme.tertiary,
- fontWeight: FontWeight.w700,
- ),
- ),
- ),
- const SizedBox(height: 4),
- Text(
- 'fix: we made the player even worse (you love)',
- style: GoogleFonts.roboto(
- color: Theme.of(context).colorScheme.tertiary,
- ),
- ),
- const SizedBox(height: 4),
- Text(
- 'chore: guhhughghu',
- style: GoogleFonts.roboto(
- color: Theme.of(context).colorScheme.tertiary,
- ),
- ),
- ],
- ),
+ return Column(
+ crossAxisAlignment: CrossAxisAlignment.start,
+ mainAxisAlignment: MainAxisAlignment.start,
+ children: [
+ FutureBuilder>(
+ future: locator().getPatchedApps(true),
+ builder: (context, snapshot) =>
+ snapshot.hasData && snapshot.data!.length > 1
+ ? ListView.builder(
+ itemBuilder: (context, index) => ApplicationItem(
+ icon: snapshot.data![index].icon,
+ name: snapshot.data![index].name,
+ patchDate: snapshot.data![index].patchDate,
+ isUpdatableApp: true,
+ onPressed: () => {},
+ ),
+ )
+ : Container(),
+ ),
+ ],
);
}
}
diff --git a/lib/ui/widgets/dashboard_raw_chip.dart b/lib/ui/widgets/dashboard_raw_chip.dart
new file mode 100644
index 00000000..960a339f
--- /dev/null
+++ b/lib/ui/widgets/dashboard_raw_chip.dart
@@ -0,0 +1,51 @@
+import 'package:flutter/material.dart';
+import 'package:flutter_i18n/flutter_i18n.dart';
+import 'package:google_fonts/google_fonts.dart';
+import 'package:revanced_manager/theme.dart';
+
+class DashboardChip extends StatelessWidget {
+ final String label;
+ final bool isSelected;
+ final Function(bool)? onSelected;
+ const DashboardChip({
+ Key? key,
+ required this.label,
+ required this.isSelected,
+ this.onSelected,
+ }) : super(key: key);
+
+ @override
+ Widget build(BuildContext context) {
+ return RawChip(
+ showCheckmark: false,
+ label: I18nText(label),
+ selected: isSelected,
+ labelStyle: GoogleFonts.inter(
+ color: isSelected
+ ? isDark
+ ? const Color(0xff95C0FE)
+ : Theme.of(context).colorScheme.secondary
+ : isDark
+ ? Colors.grey
+ : Colors.grey[700],
+ fontWeight: FontWeight.w500,
+ ),
+ backgroundColor: Colors.transparent,
+ selectedColor: const Color.fromRGBO(118, 155, 209, 0.42),
+ shape: RoundedRectangleBorder(
+ borderRadius: BorderRadius.circular(10),
+ side: BorderSide(
+ width: 1,
+ color: isDark
+ ? isSelected
+ ? const Color.fromRGBO(118, 155, 209, 0.42)
+ : Colors.grey
+ : isSelected
+ ? const Color.fromRGBO(118, 155, 209, 0.42)
+ : Colors.grey,
+ ),
+ ),
+ onSelected: onSelected,
+ );
+ }
+}
diff --git a/lib/ui/widgets/installed_apps_card.dart b/lib/ui/widgets/installed_apps_card.dart
index b77deba3..1e991975 100644
--- a/lib/ui/widgets/installed_apps_card.dart
+++ b/lib/ui/widgets/installed_apps_card.dart
@@ -1,70 +1,36 @@
import 'package:flutter/material.dart';
-import 'package:flutter_i18n/flutter_i18n.dart';
-import 'package:google_fonts/google_fonts.dart';
+import 'package:revanced_manager/app/app.locator.dart';
+import 'package:revanced_manager/models/patched_application.dart';
+import 'package:revanced_manager/ui/views/home/home_viewmodel.dart';
import 'package:revanced_manager/ui/widgets/application_item.dart';
class InstalledAppsCard extends StatelessWidget {
- final Color? color;
const InstalledAppsCard({
Key? key,
- this.color = const Color(0xff1B222B),
}) : super(key: key);
@override
Widget build(BuildContext context) {
- return Container(
- decoration: BoxDecoration(
- borderRadius: BorderRadius.circular(12),
- color: color,
- ),
- padding: const EdgeInsets.symmetric(vertical: 18, horizontal: 20),
- child: Column(
- crossAxisAlignment: CrossAxisAlignment.start,
- mainAxisAlignment: MainAxisAlignment.start,
- children: [
- I18nText(
- 'installedAppsCard.widgetTitle',
- child: Text(
- '',
- style: GoogleFonts.inter(
- fontSize: 16,
- color: Theme.of(context).colorScheme.secondary,
- fontWeight: FontWeight.w500,
- ),
- ),
- ),
- ApplicationItem(
- asset: 'assets/images/revanced.svg',
- name: 'ReVanced',
- releaseDate: '2 days ago',
- onPressed: () => {},
- ),
- I18nText(
- 'installedAppsCard.changelogLabel',
- child: Text(
- '',
- style: GoogleFonts.roboto(
- color: Theme.of(context).colorScheme.tertiary,
- fontWeight: FontWeight.w700,
- ),
- ),
- ),
- const SizedBox(height: 4),
- Text(
- 'fix: we made the player even worse (you love)',
- style: GoogleFonts.roboto(
- color: Theme.of(context).colorScheme.tertiary,
- ),
- ),
- const SizedBox(height: 4),
- Text(
- 'chore: guhhughghu',
- style: GoogleFonts.roboto(
- color: Theme.of(context).colorScheme.tertiary,
- ),
- ),
- ],
- ),
+ return Column(
+ crossAxisAlignment: CrossAxisAlignment.start,
+ mainAxisAlignment: MainAxisAlignment.start,
+ children: [
+ FutureBuilder>(
+ future: locator().getPatchedApps(false),
+ builder: (context, snapshot) =>
+ snapshot.hasData && snapshot.data!.length > 1
+ ? ListView.builder(
+ itemBuilder: (context, index) => ApplicationItem(
+ icon: snapshot.data![index].icon,
+ name: snapshot.data![index].name,
+ patchDate: snapshot.data![index].patchDate,
+ isUpdatableApp: false,
+ onPressed: () => {},
+ ),
+ )
+ : Container(),
+ ),
+ ],
);
}
}
diff --git a/lib/ui/widgets/latest_commit_card.dart b/lib/ui/widgets/latest_commit_card.dart
index e33d4946..e59d9668 100644
--- a/lib/ui/widgets/latest_commit_card.dart
+++ b/lib/ui/widgets/latest_commit_card.dart
@@ -89,6 +89,7 @@ class _LatestCommitCardState extends State {
),
onPressed: () => {},
backgroundColor: Theme.of(context).colorScheme.secondary,
+ borderColor: Theme.of(context).colorScheme.secondary,
),
],
),
diff --git a/lib/ui/widgets/patch_text_button.dart b/lib/ui/widgets/patch_text_button.dart
index 8f242f85..003a76cb 100644
--- a/lib/ui/widgets/patch_text_button.dart
+++ b/lib/ui/widgets/patch_text_button.dart
@@ -1,5 +1,7 @@
import 'package:flutter/material.dart';
+import 'package:flutter_i18n/flutter_i18n.dart';
import 'package:revanced_manager/constants.dart';
+import 'package:revanced_manager/theme.dart';
class PatchTextButton extends StatelessWidget {
final String text;
@@ -19,21 +21,30 @@ class PatchTextButton extends StatelessWidget {
return TextButton(
onPressed: onPressed,
style: Theme.of(context).textButtonTheme.style?.copyWith(
- backgroundColor: MaterialStateProperty.all(backgroundColor),
- side: MaterialStateProperty.all(
- BorderSide(
- color: borderColor,
- width: 1,
- ),
+ backgroundColor: MaterialStateProperty.all(backgroundColor),
+ side: MaterialStateProperty.all(
+ BorderSide(
+ color: borderColor,
+ width: 1,
),
),
- child: Text(
- text,
- style: interTextStyle.copyWith(
- color: backgroundColor == Colors.transparent
- ? const Color.fromRGBO(119, 146, 186, 1)
- : Colors.white),
- ),
+ padding: MaterialStateProperty.all(
+ const EdgeInsets.symmetric(
+ horizontal: 16,
+ vertical: 4,
+ ),
+ )),
+ child: I18nText(text,
+ child: Text(
+ '',
+ style: interTextStyle.copyWith(
+ color: backgroundColor == Colors.transparent
+ ? const Color.fromRGBO(119, 146, 186, 1)
+ : isDark
+ ? Colors.black
+ : Colors.white,
+ ),
+ )),
);
}
}
diff --git a/pubspec.yaml b/pubspec.yaml
index bb504833..e924b44b 100644
--- a/pubspec.yaml
+++ b/pubspec.yaml
@@ -12,8 +12,12 @@ environment:
dependencies:
app_installer: ^1.1.0
cupertino_icons: ^1.0.2
- device_apps: ^2.2.0
+ device_apps:
+ git:
+ url: https://github.com/ponces/flutter_plugin_device_apps
+ ref: appinfo-from-storage
dio: ^4.0.6
+ expandable: ^5.0.1
file_picker: ^5.0.1
flutter:
sdk: flutter
@@ -28,7 +32,6 @@ dependencies:
http: ^0.13.4
injectable: ^1.5.3
json_annotation: ^4.6.0
- package_archive_info: ^0.1.0
path_provider: ^2.0.11
root: ^2.0.2
share_extend: ^2.0.0