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 db5594d5..d6d93081 100644 --- a/android/app/src/main/kotlin/app/revanced/manager/MainActivity.kt +++ b/android/app/src/main/kotlin/app/revanced/manager/MainActivity.kt @@ -397,7 +397,7 @@ class MainActivity : FlutterActivity() { mapOf( "progress" to 1.0, "header" to "Finished!", - "log" to "Finished" + "log" to "Finished!" ) ) } diff --git a/lib/models/patched_application.dart b/lib/models/patched_application.dart index 76176ed7..65054796 100644 --- a/lib/models/patched_application.dart +++ b/lib/models/patched_application.dart @@ -18,7 +18,9 @@ class PatchedApplication { DateTime patchDate; final bool isRooted; final bool isFromStorage; + bool hasUpdates; List appliedPatches; + List changelog; PatchedApplication({ required this.name, @@ -27,9 +29,11 @@ class PatchedApplication { required this.apkFilePath, required this.icon, required this.patchDate, - required this.isRooted, - required this.isFromStorage, - required this.appliedPatches, + this.isRooted = false, + this.isFromStorage = false, + this.hasUpdates = false, + this.appliedPatches = const [], + this.changelog = const [], }); factory PatchedApplication.fromJson(Map json) => diff --git a/lib/services/manager_api.dart b/lib/services/manager_api.dart index 1febe15c..ad6dc7e1 100644 --- a/lib/services/manager_api.dart +++ b/lib/services/manager_api.dart @@ -1,6 +1,7 @@ import 'dart:convert'; import 'dart:io'; import 'package:device_apps/device_apps.dart'; +import 'package:github/github.dart'; import 'package:injectable/injectable.dart'; import 'package:package_info_plus/package_info_plus.dart'; import 'package:revanced_manager/constants.dart'; @@ -14,9 +15,11 @@ class ManagerAPI { final GithubAPI _githubAPI = GithubAPI(); final RootAPI _rootAPI = RootAPI(); late SharedPreferences _prefs; + late List _commits = []; Future initialize() async { _prefs = await SharedPreferences.getInstance(); + _commits = (await _githubAPI.getCommits(ghOrg, patchesRepo)).toList(); } Future downloadPatches(String extension) async { @@ -71,85 +74,58 @@ class ManagerAPI { setPatchedApps(patchedApps); } - void saveApp( - ApplicationWithIcon application, - bool isRooted, - bool isFromStorage, - ) { - savePatchedApp( - PatchedApplication( - name: application.appName, - packageName: application.packageName, - version: application.versionName!, - apkFilePath: application.apkFilePath, - icon: application.icon, - patchDate: DateTime.now(), - isRooted: isRooted, - isFromStorage: isFromStorage, - appliedPatches: [], - ), - ); - } - Future reAssessSavedApps() async { - List patchedApps = getPatchedApps(); bool isRoot = isRooted() ?? false; + List patchedApps = getPatchedApps(); List toRemove = []; for (PatchedApplication app in patchedApps) { - bool existsRoot = false; - if (isRoot) { - existsRoot = await _rootAPI.isAppInstalled(app.packageName); - } - bool existsNonRoot = await DeviceApps.isAppInstalled(app.packageName); - if (!existsRoot && !existsNonRoot) { + bool isRemove = await isAppUninstalled(app, isRoot); + if (isRemove) { toRemove.add(app); - } else if (existsNonRoot) { - ApplicationWithIcon? application = - await DeviceApps.getApp(app.packageName, true) - as ApplicationWithIcon?; - if (application != null) { - int savedVersionInt = - int.parse(app.version.replaceAll(RegExp('[^0-9]'), '')); - int currentVersionInt = int.parse( - application.versionName!.replaceAll(RegExp('[^0-9]'), '')); - if (savedVersionInt < currentVersionInt) { - toRemove.add(app); - } + } else { + List newChangelog = getAppChangelog( + app.packageName, + app.patchDate, + ); + if (newChangelog.isNotEmpty) { + app.changelog = newChangelog; + app.hasUpdates = true; + } else { + app.hasUpdates = false; } } } patchedApps.removeWhere((a) => toRemove.contains(a)); setPatchedApps(patchedApps); - List apps = await _rootAPI.getInstalledApps(); - for (String packageName in apps) { - if (!patchedApps.any((a) => a.packageName == packageName)) { - ApplicationWithIcon? application = - await DeviceApps.getApp(packageName, true) as ApplicationWithIcon?; - if (application != null) { - saveApp(application, true, false); - } - } + } + + Future isAppUninstalled(PatchedApplication app, bool isRoot) async { + bool existsRoot = false; + if (isRoot) { + existsRoot = await _rootAPI.isAppInstalled(app.packageName); } + bool existsNonRoot = await DeviceApps.isAppInstalled(app.packageName); + return !existsRoot && !existsNonRoot; } - Future hasAppUpdates(String packageName) async { - // TODO: get status based on last update time on the folder of this app? - return false; - } - - Future> getAppChangelog( - String packageName, - DateTime lastUpdated, - ) async { - return (await _githubAPI.getCommits(ghOrg, patchesRepo)) + List getAppChangelog(String packageName, DateTime patchedDate) { + List newCommits = _commits .where((c) => c.commit != null && c.commit!.message != null && - !c.commit!.message!.startsWith('chore') && c.commit!.author != null && - c.commit!.author!.date != null) - .map((c) => ' - ${c.commit!.message!}') - .toList() - .sublist(0, 3); + c.commit!.author!.date != null && + c.commit!.author!.date!.isAfter(patchedDate)) + .map((c) => c.commit!.message!) + .toList(); + if (newCommits.isNotEmpty) { + int firstChore = newCommits.indexWhere((c) => c.startsWith('chore')); + int secondChore = + newCommits.indexWhere((c) => c.startsWith('chore'), firstChore + 1); + if (firstChore >= 0 && secondChore > firstChore) { + return newCommits.sublist(firstChore + 1, secondChore); + } + } + return List.empty(); } } diff --git a/lib/ui/views/app_selector/app_selector_viewmodel.dart b/lib/ui/views/app_selector/app_selector_viewmodel.dart index 3e689df2..128cb4bf 100644 --- a/lib/ui/views/app_selector/app_selector_viewmodel.dart +++ b/lib/ui/views/app_selector/app_selector_viewmodel.dart @@ -35,8 +35,6 @@ class AppSelectorViewModel extends BaseViewModel { icon: application.icon, patchDate: DateTime.now(), isRooted: _isRooted, - isFromStorage: false, - appliedPatches: [], ); locator().selectedPatches.clear(); locator().notifyListeners(); @@ -63,7 +61,6 @@ class AppSelectorViewModel extends BaseViewModel { patchDate: DateTime.now(), isRooted: _isRooted, isFromStorage: true, - appliedPatches: [], ); locator().selectedPatches.clear(); locator().notifyListeners(); diff --git a/lib/ui/views/home/home_view.dart b/lib/ui/views/home/home_view.dart index e4b418cd..e5f4df78 100644 --- a/lib/ui/views/home/home_view.dart +++ b/lib/ui/views/home/home_view.dart @@ -111,8 +111,8 @@ class HomeView extends StatelessWidget { ), const SizedBox(height: 14), model.showUpdatableApps - ? AvailableUpdatesCard() - : InstalledAppsCard() + ? const AvailableUpdatesCard() + : const InstalledAppsCard() ], ), ), diff --git a/lib/ui/views/home/home_viewmodel.dart b/lib/ui/views/home/home_viewmodel.dart index c0c8a3d0..49b46bd1 100644 --- a/lib/ui/views/home/home_viewmodel.dart +++ b/lib/ui/views/home/home_viewmodel.dart @@ -22,8 +22,11 @@ class HomeViewModel extends BaseViewModel { final FlutterLocalNotificationsPlugin flutterLocalNotificationsPlugin = FlutterLocalNotificationsPlugin(); bool showUpdatableApps = true; + List patchedInstalledApps = []; + List patchedUpdatableApps = []; Future initialize() async { + await _getPatchedApps(); await _patcherAPI.loadPatches(); await flutterLocalNotificationsPlugin.initialize( const InitializationSettings( @@ -31,6 +34,7 @@ class HomeViewModel extends BaseViewModel { ), onSelectNotification: (p) => DeviceApps.openApp('app.revanced.manager'), ); + _managerAPI.reAssessSavedApps().then((_) => notifyListeners()); } void toggleUpdatableApps(bool value) { @@ -46,17 +50,16 @@ class HomeViewModel extends BaseViewModel { locator().setIndex(1); } - Future> getPatchedApps(bool isUpdatable) async { - await _managerAPI.reAssessSavedApps(); - List list = []; - List patchedApps = _managerAPI.getPatchedApps(); - for (PatchedApplication app in patchedApps) { - bool hasUpdates = await _managerAPI.hasAppUpdates(app.packageName); - if (hasUpdates == isUpdatable) { - list.add(app); - } - } - return list; + Future _getPatchedApps() async { + patchedInstalledApps = _managerAPI + .getPatchedApps() + .where((app) => app.hasUpdates == false) + .toList(); + patchedUpdatableApps = _managerAPI + .getPatchedApps() + .where((app) => app.hasUpdates == true) + .toList(); + notifyListeners(); } Future hasManagerUpdates() async { diff --git a/lib/ui/views/installer/installer_viewmodel.dart b/lib/ui/views/installer/installer_viewmodel.dart index f4fca502..cd75b581 100644 --- a/lib/ui/views/installer/installer_viewmodel.dart +++ b/lib/ui/views/installer/installer_viewmodel.dart @@ -70,10 +70,10 @@ class InstallerViewModel extends BaseViewModel { void update(double value, String header, String log) { progress = value; - isInstalled = false; isPatching = progress == 1.0 ? false : true; if (progress == 0.0) { logs = ''; + isInstalled = false; } if (header.isNotEmpty) { headerLogs = header; @@ -148,9 +148,9 @@ class InstallerViewModel extends BaseViewModel { ); isInstalled = await _patcherAPI.installPatchedFile(_app!); if (isInstalled) { - update(1.0, 'Installed!', 'Installed'); + update(1.0, 'Installed!', 'Installed!'); _app!.patchDate = DateTime.now(); - _app!.appliedPatches.addAll(_patches.map((p) => p.name).toList()); + _app!.appliedPatches = _patches.map((p) => p.name).toList(); _managerAPI.savePatchedApp(_app!); } else { update(1.0, 'Aborting...', 'An error occurred! Aborting'); diff --git a/lib/ui/widgets/homeView/available_updates_card.dart b/lib/ui/widgets/homeView/available_updates_card.dart index fdae192a..381eb540 100644 --- a/lib/ui/widgets/homeView/available_updates_card.dart +++ b/lib/ui/widgets/homeView/available_updates_card.dart @@ -1,54 +1,32 @@ import 'package:flutter/material.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:revanced_manager/ui/views/home/home_viewmodel.dart'; import 'package:revanced_manager/ui/widgets/shared/application_item.dart'; class AvailableUpdatesCard extends StatelessWidget { - AvailableUpdatesCard({ + const AvailableUpdatesCard({ Key? key, }) : super(key: key); - final ManagerAPI _managerAPI = locator(); - @override Widget build(BuildContext context) { - return Column( - crossAxisAlignment: CrossAxisAlignment.start, - mainAxisAlignment: MainAxisAlignment.start, - children: [ - FutureBuilder>( - future: locator().getPatchedApps(true), - builder: (context, snapshot) => snapshot.hasData && - snapshot.data!.isNotEmpty - ? ListView.builder( - shrinkWrap: true, - physics: const NeverScrollableScrollPhysics(), - padding: EdgeInsets.zero, - itemCount: snapshot.data!.length, - itemBuilder: (context, index) => FutureBuilder>( - future: _managerAPI.getAppChangelog( - snapshot.data![index].packageName, - snapshot.data![index].patchDate, - ), - initialData: List.empty(), - builder: (context, snapshot2) => ApplicationItem( - icon: snapshot.data![index].icon, - name: snapshot.data![index].name, - patchDate: snapshot.data![index].patchDate, - changelog: '${snapshot2.data!.join('\n')}\n...', - isUpdatableApp: true, - onPressed: () => - locator().navigateToPatcher( - snapshot.data![index], - ), - ), - ), - ) - : Container(), - ), - ], + return ListView( + shrinkWrap: true, + padding: EdgeInsets.zero, + physics: const NeverScrollableScrollPhysics(), + children: locator() + .patchedUpdatableApps + .map((app) => ApplicationItem( + icon: app.icon, + name: app.name, + patchDate: app.patchDate, + changelog: app.changelog, + isUpdatableApp: true, + onPressed: () => locator().navigateToPatcher( + app, + ), + )) + .toList(), ); } } diff --git a/lib/ui/widgets/homeView/installed_apps_card.dart b/lib/ui/widgets/homeView/installed_apps_card.dart index 7036e8e5..708a7382 100644 --- a/lib/ui/widgets/homeView/installed_apps_card.dart +++ b/lib/ui/widgets/homeView/installed_apps_card.dart @@ -1,54 +1,31 @@ import 'package:device_apps/device_apps.dart'; import 'package:flutter/material.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:revanced_manager/ui/views/home/home_viewmodel.dart'; import 'package:revanced_manager/ui/widgets/shared/application_item.dart'; class InstalledAppsCard extends StatelessWidget { - InstalledAppsCard({ + const InstalledAppsCard({ Key? key, }) : super(key: key); - final ManagerAPI _managerAPI = locator(); - @override Widget build(BuildContext context) { - return Column( - crossAxisAlignment: CrossAxisAlignment.start, - mainAxisAlignment: MainAxisAlignment.start, - children: [ - FutureBuilder>( - future: locator().getPatchedApps(false), - builder: (context, snapshot) => snapshot.hasData && - snapshot.data!.isNotEmpty - ? ListView.builder( - shrinkWrap: true, - physics: const NeverScrollableScrollPhysics(), - padding: EdgeInsets.zero, - itemCount: snapshot.data!.length, - itemBuilder: (context, index) => FutureBuilder>( - future: _managerAPI.getAppChangelog( - snapshot.data![index].packageName, - snapshot.data![index].patchDate, - ), - initialData: const ['Loading'], - builder: (context, snapshot2) => ApplicationItem( - icon: snapshot.data![index].icon, - name: snapshot.data![index].name, - patchDate: snapshot.data![index].patchDate, - changelog: '${snapshot2.data!.join('\n')}\n(...)', - isUpdatableApp: false, - onPressed: () => DeviceApps.openApp( - snapshot.data![index].packageName, - ), - ), - ), - ) - : Container(), - ), - ], + return ListView( + shrinkWrap: true, + padding: EdgeInsets.zero, + physics: const NeverScrollableScrollPhysics(), + children: locator() + .patchedInstalledApps + .map((app) => ApplicationItem( + icon: app.icon, + name: app.name, + patchDate: app.patchDate, + changelog: app.changelog, + isUpdatableApp: false, + onPressed: () => DeviceApps.openApp(app.packageName), + )) + .toList(), ); } } diff --git a/lib/ui/widgets/shared/application_item.dart b/lib/ui/widgets/shared/application_item.dart index 516cbec0..5af52da6 100644 --- a/lib/ui/widgets/shared/application_item.dart +++ b/lib/ui/widgets/shared/application_item.dart @@ -12,7 +12,7 @@ class ApplicationItem extends StatelessWidget { final Uint8List icon; final String name; final DateTime patchDate; - final String changelog; + final List changelog; final bool isUpdatableApp; final Function() onPressed; @@ -103,8 +103,9 @@ class ApplicationItem extends StatelessWidget { style: kRobotoTextStyle.copyWith(fontWeight: FontWeight.w700), ), ), + const SizedBox(height: 4), Text( - changelog, + ' - ${changelog.join('\n- ')}', style: kRobotoTextStyle, ), ],