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