feat: working patches selector and improve app selector.

This commit is contained in:
Alberto Ponces 2022-08-09 01:16:33 +01:00
parent 51801b5748
commit 33fb2a81b5
14 changed files with 228 additions and 188 deletions

View File

@ -27,7 +27,8 @@
"updateButton": "Update Manager" "updateButton": "Update Manager"
}, },
"patcherView": { "patcherView": {
"widgetTitle": "Patcher" "widgetTitle": "Patcher",
"fabButton": "Patch"
}, },
"appSelectorCard": { "appSelectorCard": {
"widgetTitle": "Select application", "widgetTitle": "Select application",
@ -42,6 +43,7 @@
"searchBarHint": "Search applications" "searchBarHint": "Search applications"
}, },
"patchesSelectorView": { "patchesSelectorView": {
"searchBarHint": "Search patches" "searchBarHint": "Search patches",
"fabButton": "Done"
} }
} }

View File

@ -12,7 +12,9 @@ import 'package:stacked_core/stacked_core.dart';
import 'package:stacked_services/src/navigation/navigation_service.dart'; import 'package:stacked_services/src/navigation/navigation_service.dart';
import '../services/patcher_api.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/patcher/patcher_viewmodel.dart';
import '../ui/views/patches_selector/patches_selector_viewmodel.dart';
final locator = StackedLocator.instance; final locator = StackedLocator.instance;
@ -26,4 +28,6 @@ Future<void> setupLocator(
locator.registerLazySingleton(() => NavigationService()); locator.registerLazySingleton(() => NavigationService());
locator.registerLazySingleton(() => PatcherService()); locator.registerLazySingleton(() => PatcherService());
locator.registerLazySingleton(() => PatcherViewModel()); locator.registerLazySingleton(() => PatcherViewModel());
locator.registerLazySingleton(() => AppSelectorViewModel());
locator.registerLazySingleton(() => PatchesSelectorViewModel());
} }

View File

@ -7,23 +7,34 @@ class GithubAPI {
var github = GitHub(); var github = GitHub();
Future<String?> latestRelease(String org, repoName) async { Future<String?> latestRelease(String org, repoName) async {
var latestRelease = await github.repositories.getLatestRelease( String? dlurl = '';
RepositorySlug(org, repoName), try {
); var latestRelease = await github.repositories.getLatestRelease(
var dlurl = latestRelease.assets RepositorySlug(org, repoName),
?.firstWhere((asset) => );
asset.name != null && dlurl = latestRelease.assets
(asset.name!.endsWith('.dex') || asset.name!.endsWith('.apk')) && ?.firstWhere((asset) =>
!asset.name!.contains('-sources') && asset.name != null &&
!asset.name!.contains('-javadoc')) (asset.name!.endsWith('.dex') || asset.name!.endsWith('.apk')) &&
.browserDownloadUrl; !asset.name!.contains('-sources') &&
!asset.name!.contains('-javadoc'))
.browserDownloadUrl;
} on Exception {
dlurl = '';
}
return dlurl; return dlurl;
} }
Future<String> latestCommitTime(String org, repoName) async { Future<String> latestCommitTime(String org, repoName) async {
var repo = await github.repositories.getRepository( String pushedAt = '';
RepositorySlug(org, repoName), try {
); var repo = await github.repositories.getRepository(
return format(repo.pushedAt!); RepositorySlug(org, repoName),
);
pushedAt = repo.pushedAt != null ? format(repo.pushedAt!) : '';
} on Exception {
pushedAt = '';
}
return pushedAt;
} }
} }

View File

@ -14,23 +14,13 @@ class PatcherService {
final List<AppInfo> _filteredPackages = []; final List<AppInfo> _filteredPackages = [];
final Map<String, List<Patch>> _filteredPatches = <String, List<Patch>>{}; final Map<String, List<Patch>> _filteredPatches = <String, List<Patch>>{};
File? _patchBundleFile; File? _patchBundleFile;
String _selectedApp = '';
List<Patch> _selectedPatches = [];
static const platform = MethodChannel('app.revanced/patcher'); static const platform = MethodChannel('app.revanced/patcher');
String getSelectedApp() => _selectedApp;
void setSelectedApp(String app) => _selectedApp = app;
List<Patch> getSelectedPatches() => _selectedPatches;
void setSelectedPatches(List<Patch> patches) => _selectedPatches = patches;
Future<void> loadPatches() async { Future<void> loadPatches() async {
if (_patchBundleFile == null) { if (_patchBundleFile == null) {
String? dexFileUrl = String? dexFileUrl =
await githubAPI.latestRelease('revanced', 'revanced-patches'); await githubAPI.latestRelease('revanced', 'revanced-patches');
if (dexFileUrl != null) { if (dexFileUrl != null && dexFileUrl.isNotEmpty) {
_patchBundleFile = _patchBundleFile =
await DefaultCacheManager().getSingleFile(dexFileUrl); await DefaultCacheManager().getSingleFile(dexFileUrl);
try { try {

View File

@ -1,8 +1,6 @@
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import 'package:flutter_i18n/flutter_i18n.dart'; import 'package:flutter_i18n/flutter_i18n.dart';
import 'package:revanced_manager/app/app.locator.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/installed_app_item.dart';
import 'package:revanced_manager/ui/widgets/search_bar.dart'; import 'package:revanced_manager/ui/widgets/search_bar.dart';
import 'package:stacked/stacked.dart'; import 'package:stacked/stacked.dart';
@ -16,92 +14,101 @@ class AppSelectorView extends StatefulWidget {
} }
class _AppSelectorViewState extends State<AppSelectorView> { class _AppSelectorViewState extends State<AppSelectorView> {
final PatcherService patcherService = locator<PatcherService>();
String query = ''; String query = '';
@override @override
Widget build(BuildContext context) { Widget build(BuildContext context) {
return ViewModelBuilder<AppSelectorViewModel>.reactive( return ViewModelBuilder<AppSelectorViewModel>.reactive(
disposeViewModel: false,
onModelReady: (model) => model.initialise(), onModelReady: (model) => model.initialise(),
viewModelBuilder: () => locator<AppSelectorViewModel>(),
builder: (context, model, child) => Scaffold( builder: (context, model, child) => Scaffold(
body: SafeArea( body: SafeArea(
child: Padding( child: Padding(
padding: padding:
const EdgeInsets.symmetric(vertical: 4.0, horizontal: 12.0), const EdgeInsets.symmetric(vertical: 4.0, horizontal: 12.0),
child: Column( child: model.apps.isNotEmpty
children: [ ? Column(
SearchBar( children: [
hintText: FlutterI18n.translate( SearchBar(
context, hintText: FlutterI18n.translate(
'appSelectorView.searchBarHint', 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<PatcherViewModel>().notifyListeners();
},
child: InstalledAppItem(
name: model.apps[index].name!,
pkgName: model.apps[index].packageName!,
icon: model.apps[index].icon!,
),
);
},
),
), ),
if (query.isNotEmpty) onQueryChanged: (searchQuery) {
model.apps.isEmpty setState(() {
? Center( query = searchQuery;
child: I18nText('appSelectorCard.noAppsLabel'), });
) },
: Expanded( ),
child: ListView.builder( const SizedBox(height: 12),
itemCount: model.apps.length, query.isEmpty || query.length < 2
itemBuilder: (context, index) { ? _getAllResults(model)
model.apps : _getFilteredResults(model)
.sort((a, b) => a.name!.compareTo(b.name!)); ],
if (model.apps[index].name! )
.toLowerCase() : query.isEmpty || query.length < 2
.contains( ? const Center(
query.toLowerCase(), child: CircularProgressIndicator(
)) { color: Color(0xff7792BA),
return InstalledAppItem(
name: model.apps[index].name!,
pkgName: model.apps[index].packageName!,
icon: model.apps[index].icon!,
);
} else {
return const SizedBox();
}
},
),
), ),
], )
), : 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();
}
},
),
); );
} }
} }

View File

@ -1,12 +1,13 @@
import 'package:installed_apps/app_info.dart'; import 'package:installed_apps/app_info.dart';
import 'package:revanced_manager/app/app.locator.dart'; import 'package:revanced_manager/app/app.locator.dart';
import 'package:revanced_manager/services/patcher_api.dart'; import 'package:revanced_manager/services/patcher_api.dart';
import 'package:revanced_manager/ui/views/patcher/patcher_viewmodel.dart';
import 'package:stacked/stacked.dart'; import 'package:stacked/stacked.dart';
class AppSelectorViewModel extends BaseViewModel { class AppSelectorViewModel extends BaseViewModel {
final PatcherService patcherService = locator<PatcherService>(); final PatcherService patcherService = locator<PatcherService>();
List<AppInfo> apps = []; List<AppInfo> apps = [];
String query = ''; AppInfo? selectedApp;
Future<void> initialise() async { Future<void> initialise() async {
await getApps(); await getApps();
@ -17,4 +18,9 @@ class AppSelectorViewModel extends BaseViewModel {
await patcherService.loadPatches(); await patcherService.loadPatches();
apps = await patcherService.getFilteredInstalledApps(); apps = await patcherService.getFilteredInstalledApps();
} }
void selectApp(AppInfo appInfo) {
locator<AppSelectorViewModel>().selectedApp = appInfo;
locator<PatcherViewModel>().notifyListeners();
}
} }

View File

@ -13,6 +13,7 @@ class HomeView extends StatelessWidget {
@override @override
Widget build(BuildContext context) { Widget build(BuildContext context) {
return ViewModelBuilder.reactive( return ViewModelBuilder.reactive(
viewModelBuilder: () => HomeViewModel(),
builder: (context, model, child) => Scaffold( builder: (context, model, child) => Scaffold(
body: SafeArea( body: SafeArea(
child: SingleChildScrollView( child: SingleChildScrollView(
@ -24,7 +25,7 @@ class HomeView extends StatelessWidget {
Align( Align(
alignment: Alignment.topRight, alignment: Alignment.topRight,
child: IconButton( child: IconButton(
onPressed: () {}, onPressed: () => {},
icon: const Icon( icon: const Icon(
Icons.more_vert, Icons.more_vert,
), ),
@ -72,7 +73,6 @@ class HomeView extends StatelessWidget {
), ),
), ),
), ),
viewModelBuilder: () => HomeViewModel(),
); );
} }
} }

View File

@ -14,13 +14,15 @@ class PatcherView extends StatelessWidget {
@override @override
Widget build(BuildContext context) { Widget build(BuildContext context) {
return ViewModelBuilder<PatcherViewModel>.reactive( return ViewModelBuilder<PatcherViewModel>.reactive(
disposeViewModel: false,
viewModelBuilder: () => locator<PatcherViewModel>(),
builder: (context, model, child) => Scaffold( builder: (context, model, child) => Scaffold(
floatingActionButton: FloatingActionButton( floatingActionButton: FloatingActionButton.extended(
onPressed: () {}, onPressed: () => {},
child: const Icon( label: I18nText('patcherView.fabButton'),
Icons.build, icon: const Icon(Icons.build),
color: Colors.white, backgroundColor: const Color(0xff7792BA),
), foregroundColor: Colors.white,
), ),
body: SafeArea( body: SafeArea(
child: Padding( child: Padding(
@ -52,7 +54,6 @@ class PatcherView extends StatelessWidget {
), ),
), ),
), ),
viewModelBuilder: () => locator<PatcherViewModel>(),
); );
} }
} }

View File

@ -1,6 +1,6 @@
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import 'package:flutter_i18n/flutter_i18n.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/views/patches_selector/patches_selector_viewmodel.dart';
import 'package:revanced_manager/ui/widgets/patch_item.dart'; import 'package:revanced_manager/ui/widgets/patch_item.dart';
import 'package:revanced_manager/ui/widgets/search_bar.dart'; import 'package:revanced_manager/ui/widgets/search_bar.dart';
@ -19,74 +19,89 @@ class _PatchesSelectorViewState extends State<PatchesSelectorView> {
@override @override
Widget build(BuildContext context) { Widget build(BuildContext context) {
return ViewModelBuilder<PatchesSelectorViewModel>.reactive( return ViewModelBuilder<PatchesSelectorViewModel>.reactive(
viewModelBuilder: () => PatchesSelectorViewModel(), disposeViewModel: false,
onModelReady: (model) => model.initialise(),
viewModelBuilder: () => locator<PatchesSelectorViewModel>(),
builder: (context, model, child) => Scaffold( builder: (context, model, child) => Scaffold(
body: Container( floatingActionButton: FloatingActionButton.extended(
margin: const EdgeInsets.fromLTRB(6.0, 26.0, 6.0, 0), onPressed: () => {},
child: Column( label: I18nText('patchesSelectorView.fabButton'),
mainAxisAlignment: MainAxisAlignment.start, icon: const Icon(Icons.check),
children: [ backgroundColor: const Color(0xff7792BA),
Padding( foregroundColor: Colors.white,
padding: const EdgeInsets.only(top: 8.0, left: 8.0, right: 8.0), ),
child: SearchBar( body: SafeArea(
hintText: FlutterI18n.translate( child: Padding(
context, padding:
'patchesSelectorView.searchBarHint', const EdgeInsets.symmetric(vertical: 4.0, horizontal: 12.0),
), child: model.patches != null && model.patches!.isNotEmpty
onQueryChanged: (searchQuery) { ? Column(
setState( children: [
() { SearchBar(
query = searchQuery; hintText: FlutterI18n.translate(
}, context,
); 'patchesSelectorView.searchBarHint',
}, ),
), onQueryChanged: (searchQuery) {
), setState(() {
Expanded( query = searchQuery;
child: FutureBuilder<List<Patch>?>( });
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();
}
}, },
); ),
} else if (snapshot.hasError) { const SizedBox(height: 12),
return Text("${snapshot.error}"); query.isEmpty || query.length < 2
} else { ? _getAllResults(model)
return const Center( : _getFilteredResults(model)
child: CircularProgressIndicator(), ],
); )
} : 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();
}
},
),
);
}
} }

View File

@ -1,21 +1,24 @@
import 'package:installed_apps/app_info.dart'; 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/app/app.locator.dart';
import 'package:revanced_manager/models/patch.dart'; import 'package:revanced_manager/models/patch.dart';
import 'package:revanced_manager/services/patcher_api.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'; import 'package:stacked/stacked.dart';
class PatchesSelectorViewModel extends BaseViewModel { class PatchesSelectorViewModel extends BaseViewModel {
final PatcherService patcherService = locator<PatcherService>(); final PatcherService patcherService = locator<PatcherService>();
AppInfo? appInfo; List<Patch>? patches = [];
List<Patch> selectedPatches = [];
Future<void> getApp() async { Future<void> initialise() async {
AppInfo app = await InstalledApps.getAppInfo("com.google.android.youtube"); await getPatches();
appInfo = app; notifyListeners();
} }
Future<List<Patch>?> getPatches() async { Future<void> getPatches() async {
getApp(); AppInfo? appInfo = locator<AppSelectorViewModel>().selectedApp;
return patcherService.getFilteredPatches(appInfo); patches = await patcherService.getFilteredPatches(appInfo);
} }
void selectPatches(List<Patch> patches) {}
} }

View File

@ -4,6 +4,7 @@ import 'package:google_fonts/google_fonts.dart';
import 'package:revanced_manager/app/app.locator.dart'; import 'package:revanced_manager/app/app.locator.dart';
import 'package:revanced_manager/constants.dart'; import 'package:revanced_manager/constants.dart';
import 'package:revanced_manager/services/patcher_api.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 { class AppSelectorCard extends StatelessWidget {
final Function()? onPressed; final Function()? onPressed;
@ -39,9 +40,9 @@ class AppSelectorCard extends StatelessWidget {
), ),
), ),
const SizedBox(height: 10), const SizedBox(height: 10),
patcherService.getSelectedApp().isNotEmpty locator<AppSelectorViewModel>().selectedApp != null
? Text( ? Text(
patcherService.getSelectedApp(), locator<AppSelectorViewModel>().selectedApp!.packageName!,
style: robotoTextStyle, style: robotoTextStyle,
) )
: I18nText( : I18nText(

View File

@ -38,7 +38,7 @@ class AvailableUpdatesCard extends StatelessWidget {
context, context,
'availableUpdatesCard.patchButton', 'availableUpdatesCard.patchButton',
), ),
onPressed: () {}, onPressed: () => {},
backgroundColor: const Color(0xff7792BA), backgroundColor: const Color(0xff7792BA),
), ),
], ],
@ -47,13 +47,13 @@ class AvailableUpdatesCard extends StatelessWidget {
asset: 'assets/images/revanced.svg', asset: 'assets/images/revanced.svg',
name: 'ReVanced', name: 'ReVanced',
releaseDate: '2 days ago', releaseDate: '2 days ago',
onPressed: () {}, onPressed: () => {},
), ),
ApplicationItem( ApplicationItem(
asset: 'assets/images/reddit.png', asset: 'assets/images/reddit.png',
name: 'ReReddit', name: 'ReReddit',
releaseDate: 'Released 1 month ago', releaseDate: 'Released 1 month ago',
onPressed: () {}, onPressed: () => {},
), ),
const SizedBox(height: 4), const SizedBox(height: 4),
I18nText( I18nText(

View File

@ -33,7 +33,7 @@ class InstalledAppsCard extends StatelessWidget {
asset: 'assets/images/revanced.svg', asset: 'assets/images/revanced.svg',
name: 'ReVanced', name: 'ReVanced',
releaseDate: '2 days ago', releaseDate: '2 days ago',
onPressed: () {}, onPressed: () => {},
), ),
I18nText( I18nText(
'installedAppsCard.changelogLabel', 'installedAppsCard.changelogLabel',

View File

@ -90,7 +90,7 @@ class _LatestCommitCardState extends State<LatestCommitCard> {
context, context,
'latestCommitCard.updateButton', 'latestCommitCard.updateButton',
), ),
onPressed: () {}, onPressed: () => {},
backgroundColor: const Color(0xff7792BA), backgroundColor: const Color(0xff7792BA),
), ),
], ],