diff --git a/assets/i18n/en.json b/assets/i18n/en.json index 291e11d6..357871cf 100644 --- a/assets/i18n/en.json +++ b/assets/i18n/en.json @@ -27,7 +27,8 @@ "updateButton": "Update Manager" }, "patcherView": { - "widgetTitle": "Patcher" + "widgetTitle": "Patcher", + "fabButton": "Patch" }, "appSelectorCard": { "widgetTitle": "Select application", @@ -42,6 +43,7 @@ "searchBarHint": "Search applications" }, "patchesSelectorView": { - "searchBarHint": "Search patches" + "searchBarHint": "Search patches", + "fabButton": "Done" } } \ No newline at end of file diff --git a/lib/app/app.locator.dart b/lib/app/app.locator.dart index b2b3ecdd..82f4362d 100644 --- a/lib/app/app.locator.dart +++ b/lib/app/app.locator.dart @@ -12,7 +12,9 @@ import 'package:stacked_core/stacked_core.dart'; import 'package:stacked_services/src/navigation/navigation_service.dart'; import '../services/patcher_api.dart'; +import '../ui/views/app_selector/app_selector_viewmodel.dart'; import '../ui/views/patcher/patcher_viewmodel.dart'; +import '../ui/views/patches_selector/patches_selector_viewmodel.dart'; final locator = StackedLocator.instance; @@ -26,4 +28,6 @@ Future setupLocator( locator.registerLazySingleton(() => NavigationService()); locator.registerLazySingleton(() => PatcherService()); locator.registerLazySingleton(() => PatcherViewModel()); + locator.registerLazySingleton(() => AppSelectorViewModel()); + locator.registerLazySingleton(() => PatchesSelectorViewModel()); } diff --git a/lib/services/github_api.dart b/lib/services/github_api.dart index 82359960..a9634d17 100644 --- a/lib/services/github_api.dart +++ b/lib/services/github_api.dart @@ -7,23 +7,34 @@ class GithubAPI { var github = GitHub(); Future latestRelease(String org, repoName) async { - var latestRelease = await github.repositories.getLatestRelease( - RepositorySlug(org, repoName), - ); - var dlurl = latestRelease.assets - ?.firstWhere((asset) => - asset.name != null && - (asset.name!.endsWith('.dex') || asset.name!.endsWith('.apk')) && - !asset.name!.contains('-sources') && - !asset.name!.contains('-javadoc')) - .browserDownloadUrl; + String? dlurl = ''; + try { + var latestRelease = await github.repositories.getLatestRelease( + RepositorySlug(org, repoName), + ); + dlurl = latestRelease.assets + ?.firstWhere((asset) => + asset.name != null && + (asset.name!.endsWith('.dex') || asset.name!.endsWith('.apk')) && + !asset.name!.contains('-sources') && + !asset.name!.contains('-javadoc')) + .browserDownloadUrl; + } on Exception { + dlurl = ''; + } return dlurl; } Future latestCommitTime(String org, repoName) async { - var repo = await github.repositories.getRepository( - RepositorySlug(org, repoName), - ); - return format(repo.pushedAt!); + String pushedAt = ''; + try { + var repo = await github.repositories.getRepository( + RepositorySlug(org, repoName), + ); + pushedAt = repo.pushedAt != null ? format(repo.pushedAt!) : ''; + } on Exception { + pushedAt = ''; + } + return pushedAt; } } diff --git a/lib/services/patcher_api.dart b/lib/services/patcher_api.dart index ddd41f47..c7bdb4cc 100644 --- a/lib/services/patcher_api.dart +++ b/lib/services/patcher_api.dart @@ -14,23 +14,13 @@ class PatcherService { final List _filteredPackages = []; final Map> _filteredPatches = >{}; File? _patchBundleFile; - String _selectedApp = ''; - List _selectedPatches = []; static const platform = MethodChannel('app.revanced/patcher'); - String getSelectedApp() => _selectedApp; - - void setSelectedApp(String app) => _selectedApp = app; - - List getSelectedPatches() => _selectedPatches; - - void setSelectedPatches(List patches) => _selectedPatches = patches; - Future loadPatches() async { if (_patchBundleFile == null) { String? dexFileUrl = await githubAPI.latestRelease('revanced', 'revanced-patches'); - if (dexFileUrl != null) { + if (dexFileUrl != null && dexFileUrl.isNotEmpty) { _patchBundleFile = await DefaultCacheManager().getSingleFile(dexFileUrl); try { diff --git a/lib/ui/views/app_selector/app_selector_view.dart b/lib/ui/views/app_selector/app_selector_view.dart index 005e4773..99389a65 100644 --- a/lib/ui/views/app_selector/app_selector_view.dart +++ b/lib/ui/views/app_selector/app_selector_view.dart @@ -1,8 +1,6 @@ import 'package:flutter/material.dart'; import 'package:flutter_i18n/flutter_i18n.dart'; import 'package:revanced_manager/app/app.locator.dart'; -import 'package:revanced_manager/services/patcher_api.dart'; -import 'package:revanced_manager/ui/views/patcher/patcher_viewmodel.dart'; import 'package:revanced_manager/ui/widgets/installed_app_item.dart'; import 'package:revanced_manager/ui/widgets/search_bar.dart'; import 'package:stacked/stacked.dart'; @@ -16,92 +14,101 @@ class AppSelectorView extends StatefulWidget { } class _AppSelectorViewState extends State { - final PatcherService patcherService = locator(); String query = ''; @override Widget build(BuildContext context) { return ViewModelBuilder.reactive( + disposeViewModel: false, onModelReady: (model) => model.initialise(), + viewModelBuilder: () => locator(), builder: (context, model, child) => Scaffold( body: SafeArea( child: Padding( padding: const EdgeInsets.symmetric(vertical: 4.0, horizontal: 12.0), - child: Column( - children: [ - SearchBar( - hintText: FlutterI18n.translate( - context, - 'appSelectorView.searchBarHint', - ), - onQueryChanged: (searchQuery) { - setState(() { - query = searchQuery; - }); - }, - ), - if (query.isEmpty || query.length < 2) - model.apps.isEmpty - ? const Center( - child: CircularProgressIndicator(), - ) - : Expanded( - child: ListView.builder( - itemCount: model.apps.length, - itemBuilder: (context, index) { - //sort alphabetically - model.apps - .sort((a, b) => a.name!.compareTo(b.name!)); - return InkWell( - onTap: () { - patcherService.setSelectedApp( - model.apps[index].packageName!); - Navigator.of(context).pop(); - locator().notifyListeners(); - }, - child: InstalledAppItem( - name: model.apps[index].name!, - pkgName: model.apps[index].packageName!, - icon: model.apps[index].icon!, - ), - ); - }, - ), + child: model.apps.isNotEmpty + ? Column( + children: [ + SearchBar( + hintText: FlutterI18n.translate( + context, + 'appSelectorView.searchBarHint', ), - if (query.isNotEmpty) - model.apps.isEmpty - ? Center( - child: I18nText('appSelectorCard.noAppsLabel'), - ) - : Expanded( - child: ListView.builder( - itemCount: model.apps.length, - itemBuilder: (context, index) { - model.apps - .sort((a, b) => a.name!.compareTo(b.name!)); - if (model.apps[index].name! - .toLowerCase() - .contains( - query.toLowerCase(), - )) { - return InstalledAppItem( - name: model.apps[index].name!, - pkgName: model.apps[index].packageName!, - icon: model.apps[index].icon!, - ); - } else { - return const SizedBox(); - } - }, - ), + onQueryChanged: (searchQuery) { + setState(() { + query = searchQuery; + }); + }, + ), + const SizedBox(height: 12), + query.isEmpty || query.length < 2 + ? _getAllResults(model) + : _getFilteredResults(model) + ], + ) + : query.isEmpty || query.length < 2 + ? const Center( + child: CircularProgressIndicator( + color: Color(0xff7792BA), ), - ], - ), + ) + : Center( + child: I18nText('appSelectorCard.noAppsLabel'), + ), ), ), ), - viewModelBuilder: () => AppSelectorViewModel(), + ); + } + + Widget _getAllResults(AppSelectorViewModel model) { + return Expanded( + child: ListView.builder( + itemCount: model.apps.length, + itemBuilder: (context, index) { + model.apps.sort((a, b) => a.name!.compareTo(b.name!)); + return InkWell( + onTap: () { + model.selectApp(model.apps[index]); + Navigator.of(context).pop(); + }, + child: InstalledAppItem( + name: model.apps[index].name!, + pkgName: model.apps[index].packageName!, + icon: model.apps[index].icon!, + ), + ); + }, + ), + ); + } + + Widget _getFilteredResults(AppSelectorViewModel model) { + return Expanded( + child: ListView.builder( + itemCount: model.apps.length, + itemBuilder: (context, index) { + model.apps.sort((a, b) => a.name!.compareTo(b.name!)); + if (model.apps[index].name!.toLowerCase().contains( + query.toLowerCase(), + )) { + return InkWell( + onTap: () { + model.selectApp(model.apps[index]); + Navigator.of(context).pop(); + }, + child: InstalledAppItem( + name: model.apps[index].name!, + pkgName: model.apps[index].packageName!, + icon: model.apps[index].icon!, + ), + ); + } else { + return const SizedBox(); + } + }, + ), ); } } diff --git a/lib/ui/views/app_selector/app_selector_viewmodel.dart b/lib/ui/views/app_selector/app_selector_viewmodel.dart index 46122faa..0c6903b8 100644 --- a/lib/ui/views/app_selector/app_selector_viewmodel.dart +++ b/lib/ui/views/app_selector/app_selector_viewmodel.dart @@ -1,12 +1,13 @@ import 'package:installed_apps/app_info.dart'; import 'package:revanced_manager/app/app.locator.dart'; import 'package:revanced_manager/services/patcher_api.dart'; +import 'package:revanced_manager/ui/views/patcher/patcher_viewmodel.dart'; import 'package:stacked/stacked.dart'; class AppSelectorViewModel extends BaseViewModel { final PatcherService patcherService = locator(); List apps = []; - String query = ''; + AppInfo? selectedApp; Future initialise() async { await getApps(); @@ -17,4 +18,9 @@ class AppSelectorViewModel extends BaseViewModel { await patcherService.loadPatches(); apps = await patcherService.getFilteredInstalledApps(); } + + void selectApp(AppInfo appInfo) { + locator().selectedApp = appInfo; + locator().notifyListeners(); + } } diff --git a/lib/ui/views/home/home_view.dart b/lib/ui/views/home/home_view.dart index 6007faf9..59e4d844 100644 --- a/lib/ui/views/home/home_view.dart +++ b/lib/ui/views/home/home_view.dart @@ -13,6 +13,7 @@ class HomeView extends StatelessWidget { @override Widget build(BuildContext context) { return ViewModelBuilder.reactive( + viewModelBuilder: () => HomeViewModel(), builder: (context, model, child) => Scaffold( body: SafeArea( child: SingleChildScrollView( @@ -24,7 +25,7 @@ class HomeView extends StatelessWidget { Align( alignment: Alignment.topRight, child: IconButton( - onPressed: () {}, + onPressed: () => {}, icon: const Icon( Icons.more_vert, ), @@ -72,7 +73,6 @@ class HomeView extends StatelessWidget { ), ), ), - viewModelBuilder: () => HomeViewModel(), ); } } diff --git a/lib/ui/views/patcher/patcher_view.dart b/lib/ui/views/patcher/patcher_view.dart index a744942d..2586d4bb 100644 --- a/lib/ui/views/patcher/patcher_view.dart +++ b/lib/ui/views/patcher/patcher_view.dart @@ -14,13 +14,15 @@ class PatcherView extends StatelessWidget { @override Widget build(BuildContext context) { return ViewModelBuilder.reactive( + disposeViewModel: false, + viewModelBuilder: () => locator(), builder: (context, model, child) => Scaffold( - floatingActionButton: FloatingActionButton( - onPressed: () {}, - child: const Icon( - Icons.build, - color: Colors.white, - ), + floatingActionButton: FloatingActionButton.extended( + onPressed: () => {}, + label: I18nText('patcherView.fabButton'), + icon: const Icon(Icons.build), + backgroundColor: const Color(0xff7792BA), + foregroundColor: Colors.white, ), body: SafeArea( child: Padding( @@ -52,7 +54,6 @@ class PatcherView extends StatelessWidget { ), ), ), - viewModelBuilder: () => locator(), ); } } diff --git a/lib/ui/views/patches_selector/patches_selector_view.dart b/lib/ui/views/patches_selector/patches_selector_view.dart index c17e9a6c..5ba42293 100644 --- a/lib/ui/views/patches_selector/patches_selector_view.dart +++ b/lib/ui/views/patches_selector/patches_selector_view.dart @@ -1,6 +1,6 @@ import 'package:flutter/material.dart'; import 'package:flutter_i18n/flutter_i18n.dart'; -import 'package:revanced_manager/models/patch.dart'; +import 'package:revanced_manager/app/app.locator.dart'; import 'package:revanced_manager/ui/views/patches_selector/patches_selector_viewmodel.dart'; import 'package:revanced_manager/ui/widgets/patch_item.dart'; import 'package:revanced_manager/ui/widgets/search_bar.dart'; @@ -19,74 +19,89 @@ class _PatchesSelectorViewState extends State { @override Widget build(BuildContext context) { return ViewModelBuilder.reactive( - viewModelBuilder: () => PatchesSelectorViewModel(), + disposeViewModel: false, + onModelReady: (model) => model.initialise(), + viewModelBuilder: () => locator(), builder: (context, model, child) => Scaffold( - body: Container( - margin: const EdgeInsets.fromLTRB(6.0, 26.0, 6.0, 0), - child: Column( - mainAxisAlignment: MainAxisAlignment.start, - children: [ - Padding( - padding: const EdgeInsets.only(top: 8.0, left: 8.0, right: 8.0), - child: SearchBar( - hintText: FlutterI18n.translate( - context, - 'patchesSelectorView.searchBarHint', - ), - onQueryChanged: (searchQuery) { - setState( - () { - query = searchQuery; - }, - ); - }, - ), - ), - Expanded( - child: FutureBuilder?>( - future: model.getPatches(), - builder: (context, snapshot) { - if (snapshot.hasData) { - return ListView.builder( - itemCount: snapshot.data!.length, - itemBuilder: (context, index) { - if (query.isEmpty || query.length < 2) { - return PatchItem( - name: snapshot.data![index].simpleName, - version: snapshot.data![index].version, - description: snapshot.data![index].description, - isSelected: false, - ); - } else if (query.isNotEmpty && - query.length >= 2 && - snapshot.data![index].simpleName - .toLowerCase() - .contains(query.toLowerCase())) { - return PatchItem( - name: snapshot.data![index].simpleName, - version: snapshot.data![index].version, - description: snapshot.data![index].description, - isSelected: false, - ); - } else { - return Container(); - } + floatingActionButton: FloatingActionButton.extended( + onPressed: () => {}, + label: I18nText('patchesSelectorView.fabButton'), + icon: const Icon(Icons.check), + backgroundColor: const Color(0xff7792BA), + foregroundColor: Colors.white, + ), + body: SafeArea( + child: Padding( + padding: + const EdgeInsets.symmetric(vertical: 4.0, horizontal: 12.0), + child: model.patches != null && model.patches!.isNotEmpty + ? Column( + children: [ + SearchBar( + hintText: FlutterI18n.translate( + context, + 'patchesSelectorView.searchBarHint', + ), + onQueryChanged: (searchQuery) { + setState(() { + query = searchQuery; + }); }, - ); - } else if (snapshot.hasError) { - return Text("${snapshot.error}"); - } else { - return const Center( - child: CircularProgressIndicator(), - ); - } - }, - ), - ), - ], + ), + const SizedBox(height: 12), + query.isEmpty || query.length < 2 + ? _getAllResults(model) + : _getFilteredResults(model) + ], + ) + : const Center( + child: CircularProgressIndicator( + color: Color(0xff7792BA), + ), + ), ), ), ), ); } + + Widget _getAllResults(PatchesSelectorViewModel model) { + return Expanded( + child: ListView.builder( + itemCount: model.patches!.length, + itemBuilder: (context, index) { + model.patches!.sort((a, b) => a.simpleName.compareTo(b.simpleName)); + return PatchItem( + name: model.patches![index].simpleName, + version: model.patches![index].version, + description: model.patches![index].description, + isSelected: false, + ); + }, + ), + ); + } + + Widget _getFilteredResults(PatchesSelectorViewModel model) { + return Expanded( + child: ListView.builder( + itemCount: model.patches!.length, + itemBuilder: (context, index) { + model.patches!.sort((a, b) => a.simpleName.compareTo(b.simpleName)); + if (model.patches![index].simpleName.toLowerCase().contains( + query.toLowerCase(), + )) { + return PatchItem( + name: model.patches![index].simpleName, + version: model.patches![index].version, + description: model.patches![index].description, + isSelected: false, + ); + } else { + return const SizedBox(); + } + }, + ), + ); + } } diff --git a/lib/ui/views/patches_selector/patches_selector_viewmodel.dart b/lib/ui/views/patches_selector/patches_selector_viewmodel.dart index 55ebc41c..5cfcc090 100644 --- a/lib/ui/views/patches_selector/patches_selector_viewmodel.dart +++ b/lib/ui/views/patches_selector/patches_selector_viewmodel.dart @@ -1,21 +1,24 @@ import 'package:installed_apps/app_info.dart'; -import 'package:installed_apps/installed_apps.dart'; import 'package:revanced_manager/app/app.locator.dart'; import 'package:revanced_manager/models/patch.dart'; import 'package:revanced_manager/services/patcher_api.dart'; +import 'package:revanced_manager/ui/views/app_selector/app_selector_viewmodel.dart'; import 'package:stacked/stacked.dart'; class PatchesSelectorViewModel extends BaseViewModel { final PatcherService patcherService = locator(); - AppInfo? appInfo; + List? patches = []; + List selectedPatches = []; - Future getApp() async { - AppInfo app = await InstalledApps.getAppInfo("com.google.android.youtube"); - appInfo = app; + Future initialise() async { + await getPatches(); + notifyListeners(); } - Future?> getPatches() async { - getApp(); - return patcherService.getFilteredPatches(appInfo); + Future getPatches() async { + AppInfo? appInfo = locator().selectedApp; + patches = await patcherService.getFilteredPatches(appInfo); } + + void selectPatches(List patches) {} } diff --git a/lib/ui/widgets/app_selector_card.dart b/lib/ui/widgets/app_selector_card.dart index 89109f09..9b20d409 100644 --- a/lib/ui/widgets/app_selector_card.dart +++ b/lib/ui/widgets/app_selector_card.dart @@ -4,6 +4,7 @@ import 'package:google_fonts/google_fonts.dart'; import 'package:revanced_manager/app/app.locator.dart'; import 'package:revanced_manager/constants.dart'; import 'package:revanced_manager/services/patcher_api.dart'; +import 'package:revanced_manager/ui/views/app_selector/app_selector_viewmodel.dart'; class AppSelectorCard extends StatelessWidget { final Function()? onPressed; @@ -39,9 +40,9 @@ class AppSelectorCard extends StatelessWidget { ), ), const SizedBox(height: 10), - patcherService.getSelectedApp().isNotEmpty + locator().selectedApp != null ? Text( - patcherService.getSelectedApp(), + locator().selectedApp!.packageName!, style: robotoTextStyle, ) : I18nText( diff --git a/lib/ui/widgets/available_updates_card.dart b/lib/ui/widgets/available_updates_card.dart index b5a5da19..a7a44f86 100644 --- a/lib/ui/widgets/available_updates_card.dart +++ b/lib/ui/widgets/available_updates_card.dart @@ -38,7 +38,7 @@ class AvailableUpdatesCard extends StatelessWidget { context, 'availableUpdatesCard.patchButton', ), - onPressed: () {}, + onPressed: () => {}, backgroundColor: const Color(0xff7792BA), ), ], @@ -47,13 +47,13 @@ class AvailableUpdatesCard extends StatelessWidget { asset: 'assets/images/revanced.svg', name: 'ReVanced', releaseDate: '2 days ago', - onPressed: () {}, + onPressed: () => {}, ), ApplicationItem( asset: 'assets/images/reddit.png', name: 'ReReddit', releaseDate: 'Released 1 month ago', - onPressed: () {}, + onPressed: () => {}, ), const SizedBox(height: 4), I18nText( diff --git a/lib/ui/widgets/installed_apps_card.dart b/lib/ui/widgets/installed_apps_card.dart index ad5fa4ae..6b75751c 100644 --- a/lib/ui/widgets/installed_apps_card.dart +++ b/lib/ui/widgets/installed_apps_card.dart @@ -33,7 +33,7 @@ class InstalledAppsCard extends StatelessWidget { asset: 'assets/images/revanced.svg', name: 'ReVanced', releaseDate: '2 days ago', - onPressed: () {}, + onPressed: () => {}, ), I18nText( 'installedAppsCard.changelogLabel', diff --git a/lib/ui/widgets/latest_commit_card.dart b/lib/ui/widgets/latest_commit_card.dart index 6134b26c..30645adc 100644 --- a/lib/ui/widgets/latest_commit_card.dart +++ b/lib/ui/widgets/latest_commit_card.dart @@ -90,7 +90,7 @@ class _LatestCommitCardState extends State { context, 'latestCommitCard.updateButton', ), - onPressed: () {}, + onPressed: () => {}, backgroundColor: const Color(0xff7792BA), ), ],