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/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/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..295380ba 100644 --- a/lib/ui/views/home/home_view.dart +++ b/lib/ui/views/home/home_view.dart @@ -1,6 +1,7 @@ import 'package:flutter/material.dart'; import 'package:flutter_i18n/flutter_i18n.dart'; import 'package:google_fonts/google_fonts.dart'; +import 'package: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'; @@ -14,7 +15,8 @@ class HomeView extends StatelessWidget { @override Widget build(BuildContext context) { return ViewModelBuilder.reactive( - viewModelBuilder: () => HomeViewModel(), + disposeViewModel: false, + viewModelBuilder: () => locator(), builder: (context, model, child) => Scaffold( body: SafeArea( child: SingleChildScrollView( diff --git a/lib/ui/views/home/home_viewmodel.dart b/lib/ui/views/home/home_viewmodel.dart index 79882510..c2e958e2 100644 --- a/lib/ui/views/home/home_viewmodel.dart +++ b/lib/ui/views/home/home_viewmodel.dart @@ -1,8 +1,22 @@ +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 { Future downloadPatches() => locator().downloadPatches(); Future downloadIntegrations() => locator().downloadIntegrations(); + + Future> getPatchedApps() 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..7c30088a 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 { @@ -61,29 +62,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,29 +92,23 @@ 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); } @@ -128,13 +119,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 +136,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 +173,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..4c5c49db 100644 --- a/lib/ui/widgets/application_item.dart +++ b/lib/ui/widgets/application_item.dart @@ -1,40 +1,30 @@ +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/ui/widgets/patch_text_button.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 Function()? onPressed; const ApplicationItem({ Key? key, - required this.asset, + required this.icon, required this.name, - required this.releaseDate, + required this.patchDate, 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, - ), + leading: Image.memory(icon), title: Text( name, style: GoogleFonts.roboto( @@ -43,7 +33,7 @@ class ApplicationItem extends StatelessWidget { ), ), subtitle: Text( - releaseDate, + format(patchDate), style: robotoTextStyle, ), trailing: PatchTextButton( diff --git a/lib/ui/widgets/available_updates_card.dart b/lib/ui/widgets/available_updates_card.dart index d2e05b7f..075e578d 100644 --- a/lib/ui/widgets/available_updates_card.dart +++ b/lib/ui/widgets/available_updates_card.dart @@ -1,6 +1,9 @@ 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'; @@ -50,17 +53,19 @@ class AvailableUpdatesCard extends StatelessWidget { ], ), ), - 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: () => {}, + FutureBuilder>( + future: locator().getPatchedApps(), + 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, + onPressed: () => {}, + ), + ) + : Container(), ), const SizedBox(height: 4), I18nText( diff --git a/lib/ui/widgets/installed_apps_card.dart b/lib/ui/widgets/installed_apps_card.dart index b77deba3..a69971b8 100644 --- a/lib/ui/widgets/installed_apps_card.dart +++ b/lib/ui/widgets/installed_apps_card.dart @@ -1,6 +1,9 @@ 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 { @@ -33,11 +36,19 @@ class InstalledAppsCard extends StatelessWidget { ), ), ), - ApplicationItem( - asset: 'assets/images/revanced.svg', - name: 'ReVanced', - releaseDate: '2 days ago', - onPressed: () => {}, + FutureBuilder>( + future: locator().getPatchedApps(), + 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, + onPressed: () => {}, + ), + ) + : Container(), ), I18nText( 'installedAppsCard.changelogLabel', diff --git a/pubspec.yaml b/pubspec.yaml index bb504833..09ee05e3 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -12,7 +12,10 @@ 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 file_picker: ^5.0.1 flutter: @@ -28,7 +31,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