diff --git a/assets/i18n/en.json b/assets/i18n/en.json index f9e1cc73..0ca59e0f 100644 --- a/assets/i18n/en.json +++ b/assets/i18n/en.json @@ -28,11 +28,13 @@ }, "appSelectorCard": { "widgetTitle": "Select application", + "widgetTitleSelected": "Selected application", "widgetSubtitle": "No application selected.", "noAppsLabel": "No apps found." }, "patchSelectorCard": { "widgetTitle": "Select patches", + "widgetTitleSelected": "Selected patches", "widgetSubtitle": "Select an application first.", "widgetEmptySubtitle": "No patches selected." }, diff --git a/lib/models/patched_application.dart b/lib/models/patched_application.dart index 73e5d5f9..cf5871f6 100644 --- a/lib/models/patched_application.dart +++ b/lib/models/patched_application.dart @@ -1,3 +1,4 @@ +import 'dart:convert'; import 'dart:typed_data'; import 'package:json_annotation/json_annotation.dart'; @@ -10,8 +11,8 @@ class PatchedApplication { final String version; final String apkFilePath; @JsonKey( - fromJson: bytesFromString, - toJson: bytesToString, + fromJson: decodeBase64, + toJson: encodeBase64, ) final Uint8List icon; final DateTime patchDate; @@ -36,8 +37,7 @@ class PatchedApplication { Map toJson() => _$PatchedApplicationToJson(this); - static Uint8List bytesFromString(String icon) => - Uint8List.fromList(icon.codeUnits); + static Uint8List decodeBase64(String icon) => base64.decode(icon); - static String bytesToString(Uint8List bytes) => String.fromCharCodes(bytes); + static String encodeBase64(Uint8List bytes) => base64.encode(bytes); } diff --git a/lib/services/github_api.dart b/lib/services/github_api.dart index 3335d29d..59e6501e 100644 --- a/lib/services/github_api.dart +++ b/lib/services/github_api.dart @@ -1,5 +1,6 @@ import 'package:github/github.dart'; import 'package:injectable/injectable.dart'; +import 'package:revanced_manager/models/patched_application.dart'; import 'package:timeago/timeago.dart'; @lazySingleton @@ -7,12 +8,11 @@ class GithubAPI { var github = GitHub(); Future latestRelease(String org, repoName) async { - String? dlurl = ''; try { var latestRelease = await github.repositories.getLatestRelease( RepositorySlug(org, repoName), ); - dlurl = latestRelease.assets + return latestRelease.assets ?.firstWhere((asset) => asset.name != null && (asset.name!.endsWith('.dex') || asset.name!.endsWith('.apk')) && @@ -20,24 +20,21 @@ class GithubAPI { !asset.name!.contains('-javadoc')) .browserDownloadUrl; } on Exception { - dlurl = ''; + return ''; } - return dlurl; } Future latestCommitTime(String org, repoName) async { - String pushedAt = ''; try { var repo = await github.repositories.getRepository( RepositorySlug(org, repoName), ); - pushedAt = repo.pushedAt != null + return repo.pushedAt != null ? format(repo.pushedAt!, locale: 'en_short') : ''; } on Exception { - pushedAt = ''; + return ''; } - return pushedAt; } Future> getContributors(String org, repoName) async { @@ -50,4 +47,15 @@ class GithubAPI { return List.empty(); } } + + Future hasUpdates(PatchedApplication app, String org, repoName) async { + // TODO: get status based on last update time on the folder of this app? + return true; + } + + Future getChangelog( + PatchedApplication app, String org, repoName) async { + // TODO: get changelog based on last commits on the folder of this app? + return 'fix: incorrect fingerprint version'; + } } diff --git a/lib/services/patcher_api.dart b/lib/services/patcher_api.dart index 5c1223a0..bf641616 100644 --- a/lib/services/patcher_api.dart +++ b/lib/services/patcher_api.dart @@ -206,7 +206,7 @@ class PatcherAPI { ); } else { await AppInstaller.installApk(_outFile!.path); - return true; + return await DeviceApps.isAppInstalled(patchedApp.packageName); } } on Exception { return false; diff --git a/lib/ui/views/home/home_view.dart b/lib/ui/views/home/home_view.dart index 2b4f1c88..6978fd00 100644 --- a/lib/ui/views/home/home_view.dart +++ b/lib/ui/views/home/home_view.dart @@ -90,8 +90,8 @@ class HomeView extends StatelessWidget { ), const SizedBox(height: 14), model.showUpdatableApps - ? const AvailableUpdatesCard() - : const InstalledAppsCard() + ? AvailableUpdatesCard() + : InstalledAppsCard() ], ), ), diff --git a/lib/ui/views/home/home_viewmodel.dart b/lib/ui/views/home/home_viewmodel.dart index e9929eed..07a58c36 100644 --- a/lib/ui/views/home/home_viewmodel.dart +++ b/lib/ui/views/home/home_viewmodel.dart @@ -2,12 +2,14 @@ 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/github_api.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 { + final GithubAPI githubAPI = GithubAPI(); bool showUpdatableApps = true; void toggleUpdatableApps(bool value) { @@ -19,10 +21,20 @@ class HomeViewModel extends BaseViewModel { Future downloadIntegrations() => locator().downloadIntegrations(); Future> getPatchedApps(bool isUpdatable) async { + List list = []; SharedPreferences prefs = await SharedPreferences.getInstance(); List patchedApps = prefs.getStringList('patchedApps') ?? []; - return patchedApps - .map((app) => PatchedApplication.fromJson(json.decode(app))) - .toList(); + for (String str in patchedApps) { + PatchedApplication app = PatchedApplication.fromJson(json.decode(str)); + bool hasUpdates = await githubAPI.hasUpdates( + app, + 'revanced', + 'revanced-patches', + ); + if (hasUpdates == isUpdatable) { + list.add(app); + } + } + return list; } } diff --git a/lib/ui/views/installer/installer_view.dart b/lib/ui/views/installer/installer_view.dart index 5aaadbcd..28bb8517 100644 --- a/lib/ui/views/installer/installer_view.dart +++ b/lib/ui/views/installer/installer_view.dart @@ -1,6 +1,5 @@ import 'package:flutter/material.dart'; import 'package:flutter_i18n/flutter_i18n.dart'; -import 'package:fluttertoast/fluttertoast.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'; @@ -84,7 +83,7 @@ class InstallerView extends StatelessWidget { child: SelectableText( model.logs, style: GoogleFonts.jetBrainsMono( - fontSize: 12, + fontSize: 13, height: 1.5, ), ), diff --git a/lib/ui/views/installer/installer_viewmodel.dart b/lib/ui/views/installer/installer_viewmodel.dart index f25f19db..b95df9e6 100644 --- a/lib/ui/views/installer/installer_viewmodel.dart +++ b/lib/ui/views/installer/installer_viewmodel.dart @@ -1,3 +1,4 @@ +import 'dart:convert'; import 'package:device_apps/device_apps.dart'; import 'package:flutter_background/flutter_background.dart'; import 'package:revanced_manager/app/app.locator.dart'; @@ -151,7 +152,7 @@ class InstallerViewModel extends BaseViewModel { Future saveApp(PatchedApplication selectedApp) async { SharedPreferences prefs = await SharedPreferences.getInstance(); List patchedApps = prefs.getStringList('patchedApps') ?? []; - String app = selectedApp.toJson().toString(); + String app = json.encode(selectedApp.toJson()); if (!patchedApps.contains(app)) { patchedApps.add(app); prefs.setStringList('patchedApps', patchedApps); diff --git a/lib/ui/widgets/app_selector_card.dart b/lib/ui/widgets/app_selector_card.dart index a4f57020..b23598de 100644 --- a/lib/ui/widgets/app_selector_card.dart +++ b/lib/ui/widgets/app_selector_card.dart @@ -1,3 +1,5 @@ +import 'dart:typed_data'; + import 'package:flutter/material.dart'; import 'package:flutter_i18n/flutter_i18n.dart'; import 'package:google_fonts/google_fonts.dart'; @@ -32,8 +34,11 @@ class AppSelectorCard extends StatelessWidget { child: Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ + const SizedBox(height: 10), I18nText( - 'appSelectorCard.widgetTitle', + locator().selectedApp == null + ? 'appSelectorCard.widgetTitle' + : 'appSelectorCard.widgetTitleSelected', child: Text( '', style: GoogleFonts.roboto( @@ -43,17 +48,35 @@ class AppSelectorCard extends StatelessWidget { ), ), const SizedBox(height: 10), - locator().selectedApp != null - ? Text( - _getAppSelection(), - style: robotoTextStyle, - ) - : I18nText( + locator().selectedApp == null + ? I18nText( 'appSelectorCard.widgetSubtitle', child: Text( '', style: robotoTextStyle, ), + ) + : Row( + children: [ + SizedBox( + height: 16.0, + child: ClipOval( + child: Image.memory( + locator().selectedApp == null + ? Uint8List(0) + : locator() + .selectedApp! + .icon, + fit: BoxFit.cover, + ), + ), + ), + const SizedBox(width: 4), + Text( + _getAppSelection(), + style: robotoTextStyle, + ), + ], ), ], ), diff --git a/lib/ui/widgets/application_item.dart b/lib/ui/widgets/application_item.dart index d9f462ad..21e3fea8 100644 --- a/lib/ui/widgets/application_item.dart +++ b/lib/ui/widgets/application_item.dart @@ -12,7 +12,7 @@ class ApplicationItem extends StatelessWidget { final Uint8List icon; final String name; final DateTime patchDate; - final String? changelog; + final String changelog; final bool isUpdatableApp; final Function()? onPressed; @@ -21,7 +21,7 @@ class ApplicationItem extends StatelessWidget { required this.icon, required this.name, required this.patchDate, - this.changelog = '', + required this.changelog, required this.isUpdatableApp, required this.onPressed, }) : super(key: key); @@ -66,7 +66,7 @@ class ApplicationItem extends StatelessWidget { ), ), Text( - format(patchDate), + format(patchDate, locale: 'en_short'), style: robotoTextStyle.copyWith( color: Theme.of(context).colorScheme.tertiary, ), @@ -104,7 +104,7 @@ class ApplicationItem extends StatelessWidget { ), ), Text( - changelog!, + changelog, style: robotoTextStyle, ), ], diff --git a/lib/ui/widgets/available_updates_card.dart b/lib/ui/widgets/available_updates_card.dart index 00a7e0c0..662879b7 100644 --- a/lib/ui/widgets/available_updates_card.dart +++ b/lib/ui/widgets/available_updates_card.dart @@ -1,14 +1,17 @@ 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/github_api.dart'; import 'package:revanced_manager/ui/views/home/home_viewmodel.dart'; import 'package:revanced_manager/ui/widgets/application_item.dart'; class AvailableUpdatesCard extends StatelessWidget { - const AvailableUpdatesCard({ + AvailableUpdatesCard({ Key? key, }) : super(key: key); + final GithubAPI githubAPI = GithubAPI(); + @override Widget build(BuildContext context) { return Column( @@ -18,14 +21,26 @@ class AvailableUpdatesCard extends StatelessWidget { FutureBuilder>( future: locator().getPatchedApps(true), builder: (context, snapshot) => - snapshot.hasData && snapshot.data!.length > 1 + snapshot.hasData && snapshot.data!.isNotEmpty ? ListView.builder( - itemBuilder: (context, index) => ApplicationItem( - icon: snapshot.data![index].icon, - name: snapshot.data![index].name, - patchDate: snapshot.data![index].patchDate, - isUpdatableApp: true, - onPressed: () => {}, + shrinkWrap: true, + physics: const NeverScrollableScrollPhysics(), + itemCount: snapshot.data!.length, + itemBuilder: (context, index) => FutureBuilder( + future: githubAPI.getChangelog( + snapshot.data![index], + 'revanced', + 'revanced-patches', + ), + initialData: '', + builder: (context, snapshot2) => ApplicationItem( + icon: snapshot.data![index].icon, + name: snapshot.data![index].name, + patchDate: snapshot.data![index].patchDate, + changelog: snapshot2.data!, + isUpdatableApp: true, + onPressed: () => {}, + ), ), ) : Container(), diff --git a/lib/ui/widgets/installed_apps_card.dart b/lib/ui/widgets/installed_apps_card.dart index 1e991975..4d985663 100644 --- a/lib/ui/widgets/installed_apps_card.dart +++ b/lib/ui/widgets/installed_apps_card.dart @@ -1,14 +1,17 @@ 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/github_api.dart'; import 'package:revanced_manager/ui/views/home/home_viewmodel.dart'; import 'package:revanced_manager/ui/widgets/application_item.dart'; class InstalledAppsCard extends StatelessWidget { - const InstalledAppsCard({ + InstalledAppsCard({ Key? key, }) : super(key: key); + final GithubAPI githubAPI = GithubAPI(); + @override Widget build(BuildContext context) { return Column( @@ -18,14 +21,26 @@ class InstalledAppsCard extends StatelessWidget { FutureBuilder>( future: locator().getPatchedApps(false), builder: (context, snapshot) => - snapshot.hasData && snapshot.data!.length > 1 + snapshot.hasData && snapshot.data!.isNotEmpty ? ListView.builder( - itemBuilder: (context, index) => ApplicationItem( - icon: snapshot.data![index].icon, - name: snapshot.data![index].name, - patchDate: snapshot.data![index].patchDate, - isUpdatableApp: false, - onPressed: () => {}, + shrinkWrap: true, + physics: const NeverScrollableScrollPhysics(), + itemCount: snapshot.data!.length, + itemBuilder: (context, index) => FutureBuilder( + future: githubAPI.getChangelog( + snapshot.data![index], + 'revanced', + 'revanced-patches', + ), + initialData: '', + builder: (context, snapshot2) => ApplicationItem( + icon: snapshot.data![index].icon, + name: snapshot.data![index].name, + patchDate: snapshot.data![index].patchDate, + changelog: snapshot2.data!, + isUpdatableApp: false, + onPressed: () => {}, + ), ), ) : Container(), diff --git a/lib/ui/widgets/patch_selector_card.dart b/lib/ui/widgets/patch_selector_card.dart index 0c9fee98..a4ab8fdc 100644 --- a/lib/ui/widgets/patch_selector_card.dart +++ b/lib/ui/widgets/patch_selector_card.dart @@ -32,7 +32,9 @@ class PatchSelectorCard extends StatelessWidget { crossAxisAlignment: CrossAxisAlignment.start, children: [ I18nText( - 'patchSelectorCard.widgetTitle', + locator().selectedPatches.isEmpty + ? 'patchSelectorCard.widgetTitle' + : 'patchSelectorCard.widgetTitleSelected', child: Text( '', style: GoogleFonts.roboto(