add page for aur search
install aur package by button click
This commit is contained in:
		
							
								
								
									
										15
									
								
								frontend/lib/api/aur.dart
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										15
									
								
								frontend/lib/api/aur.dart
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,15 @@ | ||||
| import 'package:aurcache/models/aur_package.dart'; | ||||
|  | ||||
| import 'api_client.dart'; | ||||
|  | ||||
| extension AURApi on ApiClient { | ||||
|   Future<List<AurPackage>> getAurPackages(String query) async { | ||||
|     final resp = await getRawClient().get("/search?query=$query"); | ||||
|  | ||||
|     final responseObject = resp.data as List; | ||||
|     final List<AurPackage> packages = responseObject | ||||
|         .map((e) => AurPackage.fromJson(e)) | ||||
|         .toList(growable: false); | ||||
|     return packages; | ||||
|   } | ||||
| } | ||||
| @@ -26,6 +26,15 @@ class _APIBuilderState<T extends BaseProvider, K, DTO> | ||||
|     extends State<APIBuilder<T, K, DTO>> { | ||||
|   Timer? timer; | ||||
|  | ||||
|   @override | ||||
|   void didUpdateWidget(APIBuilder<T, K, DTO> oldWidget) { | ||||
|     if (oldWidget.dto != widget.dto) { | ||||
|       Provider.of<T>(context, listen: false) | ||||
|           .loadFuture(context, dto: widget.dto); | ||||
|     } | ||||
|     super.didUpdateWidget(oldWidget); | ||||
|   } | ||||
|  | ||||
|   @override | ||||
|   void initState() { | ||||
|     super.initState(); | ||||
|   | ||||
							
								
								
									
										56
									
								
								frontend/lib/components/aur_search_table.dart
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										56
									
								
								frontend/lib/components/aur_search_table.dart
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,56 @@ | ||||
| import 'package:aurcache/api/packages.dart'; | ||||
| import 'package:aurcache/models/aur_package.dart'; | ||||
| import 'package:flutter/material.dart'; | ||||
| import 'package:go_router/go_router.dart'; | ||||
| import '../api/API.dart'; | ||||
| import '../constants/color_constants.dart'; | ||||
| import 'confirm_popup.dart'; | ||||
|  | ||||
| class AurSearchTable extends StatelessWidget { | ||||
|   const AurSearchTable({super.key, required this.data}); | ||||
|   final List<AurPackage> data; | ||||
|  | ||||
|   @override | ||||
|   Widget build(BuildContext context) { | ||||
|     return DataTable( | ||||
|         horizontalMargin: 0, | ||||
|         columnSpacing: defaultPadding, | ||||
|         columns: const [ | ||||
|           DataColumn( | ||||
|             label: Text("Package Name"), | ||||
|           ), | ||||
|           DataColumn( | ||||
|             label: Text("Version"), | ||||
|           ), | ||||
|           DataColumn( | ||||
|             label: Text("Action"), | ||||
|           ), | ||||
|         ], | ||||
|         rows: | ||||
|             data.map((e) => buildDataRow(e, context)).toList(growable: false)); | ||||
|   } | ||||
|  | ||||
|   DataRow buildDataRow(AurPackage package, BuildContext context) { | ||||
|     return DataRow( | ||||
|       cells: [ | ||||
|         DataCell(Text(package.name)), | ||||
|         DataCell(Text(package.version.toString())), | ||||
|         DataCell( | ||||
|           TextButton( | ||||
|             child: const Text("Install", style: TextStyle(color: greenColor)), | ||||
|             onPressed: () async { | ||||
|               final confirmResult = await showConfirmationDialog( | ||||
|                   context, | ||||
|                   "Install Package?", | ||||
|                   "Are you sure to install Package: ${package.name}", () async { | ||||
|                 await API.addPackage(name: package.name); | ||||
|                 context.go("/"); | ||||
|               }, null); | ||||
|               if (!confirmResult) return; | ||||
|             }, | ||||
|           ), | ||||
|         ), | ||||
|       ], | ||||
|     ); | ||||
|   } | ||||
| } | ||||
| @@ -1,6 +1,12 @@ | ||||
| import 'package:flutter/material.dart'; | ||||
|  | ||||
| Future<bool> showDeleteConfirmationDialog(BuildContext context) async { | ||||
| Future<bool> showConfirmationDialog( | ||||
|   BuildContext context, | ||||
|   String title, | ||||
|   String content, | ||||
|   void Function() successCallback, | ||||
|   void Function()? errorCallback, | ||||
| ) async { | ||||
|   return (await showDialog<bool>( | ||||
|     context: context, | ||||
|     barrierDismissible: false, | ||||
| @@ -17,20 +23,24 @@ Future<bool> showDeleteConfirmationDialog(BuildContext context) async { | ||||
|           ), | ||||
|           // Delete confirmation dialog | ||||
|           AlertDialog( | ||||
|             title: Text('Confirm Delete'), | ||||
|             content: Text('Are you sure you want to delete this item?'), | ||||
|             title: Text(title), | ||||
|             content: Text(content), | ||||
|             actions: <Widget>[ | ||||
|               TextButton( | ||||
|                 onPressed: () { | ||||
|                   Navigator.of(context).pop(true); | ||||
|                   successCallback(); | ||||
|                 }, | ||||
|                 child: Text('Yes, Delete'), | ||||
|                 child: const Text('Yes'), | ||||
|               ), | ||||
|               TextButton( | ||||
|                 onPressed: () { | ||||
|                   Navigator.of(context).pop(false); // Dismiss dialog | ||||
|                   if (errorCallback != null) { | ||||
|                     errorCallback(); | ||||
|                   } | ||||
|                 }, | ||||
|                 child: Text('Cancel'), | ||||
|                 child: const Text('Cancel'), | ||||
|               ), | ||||
|             ], | ||||
|           ), | ||||
|   | ||||
| @@ -87,10 +87,8 @@ class PackagesTable extends StatelessWidget { | ||||
|                 child: const Text("Delete", | ||||
|                     style: TextStyle(color: Colors.redAccent)), | ||||
|                 onPressed: () async { | ||||
|                   final confirmResult = | ||||
|                       await showDeleteConfirmationDialog(context); | ||||
|                   if (!confirmResult) return; | ||||
|  | ||||
|                   await showConfirmationDialog(context, "Delete Package", | ||||
|                       "Are you sure to delete this Package?", () async { | ||||
|                     final succ = await API.deletePackage(package.id); | ||||
|                     if (succ) { | ||||
|                       Provider.of<PackagesProvider>(context, listen: false) | ||||
| @@ -100,6 +98,7 @@ class PackagesTable extends StatelessWidget { | ||||
|                       Provider.of<StatsProvider>(context, listen: false) | ||||
|                           .refresh(context); | ||||
|                     } | ||||
|                   }, null); | ||||
|                 }, | ||||
|               ), | ||||
|             ], | ||||
|   | ||||
| @@ -1,3 +1,4 @@ | ||||
| import 'package:aurcache/screens/aur_screen.dart'; | ||||
| import 'package:aurcache/screens/build_screen.dart'; | ||||
| import 'package:aurcache/screens/builds_screen.dart'; | ||||
| import 'package:aurcache/screens/dashboard_screen.dart'; | ||||
| @@ -40,6 +41,10 @@ final appRouter = GoRouter( | ||||
|           path: '/packages', | ||||
|           builder: (context, state) => const PackagesScreen(), | ||||
|         ), | ||||
|         GoRoute( | ||||
|           path: '/aur', | ||||
|           builder: (context, state) => AurScreen(), | ||||
|         ), | ||||
|         GoRoute( | ||||
|           path: '/package/:id', | ||||
|           builder: (context, state) { | ||||
|   | ||||
| @@ -43,12 +43,16 @@ class SideMenu extends StatelessWidget { | ||||
|             DrawerListTile( | ||||
|               title: "Builds", | ||||
|               svgSrc: "assets/icons/menu_tran.svg", | ||||
|               press: () {}, | ||||
|               press: () { | ||||
|                 context.go("/builds"); | ||||
|               }, | ||||
|             ), | ||||
|             DrawerListTile( | ||||
|               title: "AUR", | ||||
|               svgSrc: "assets/icons/menu_task.svg", | ||||
|               press: () {}, | ||||
|               press: () { | ||||
|                 context.go("/aur"); | ||||
|               }, | ||||
|             ), | ||||
|             DrawerListTile( | ||||
|               title: "Settings", | ||||
|   | ||||
							
								
								
									
										12
									
								
								frontend/lib/models/aur_package.dart
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										12
									
								
								frontend/lib/models/aur_package.dart
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,12 @@ | ||||
| class AurPackage { | ||||
|   final String name, version; | ||||
|  | ||||
|   AurPackage({required this.name, required this.version}); | ||||
|  | ||||
|   factory AurPackage.fromJson(Map<String, dynamic> json) { | ||||
|     return AurPackage( | ||||
|       name: json["name"] as String, | ||||
|       version: json["version"] as String, | ||||
|     ); | ||||
|   } | ||||
| } | ||||
							
								
								
									
										18
									
								
								frontend/lib/providers/aur_search_provider.dart
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										18
									
								
								frontend/lib/providers/aur_search_provider.dart
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,18 @@ | ||||
| import 'package:aurcache/api/aur.dart'; | ||||
| import 'package:aurcache/models/aur_package.dart'; | ||||
|  | ||||
| import '../api/API.dart'; | ||||
| import 'BaseProvider.dart'; | ||||
|  | ||||
| class AurSearchDTO { | ||||
|   final String query; | ||||
|  | ||||
|   AurSearchDTO({required this.query}); | ||||
| } | ||||
|  | ||||
| class AURSearchProvider extends BaseProvider<List<AurPackage>, AurSearchDTO> { | ||||
|   @override | ||||
|   loadFuture(context, {dto}) { | ||||
|     data = API.getAurPackages(dto!.query); | ||||
|   } | ||||
| } | ||||
							
								
								
									
										93
									
								
								frontend/lib/screens/aur_screen.dart
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										93
									
								
								frontend/lib/screens/aur_screen.dart
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,93 @@ | ||||
| import 'dart:async'; | ||||
|  | ||||
| import 'package:aurcache/components/aur_search_table.dart'; | ||||
| import 'package:aurcache/models/aur_package.dart'; | ||||
| import 'package:aurcache/providers/aur_search_provider.dart'; | ||||
| import 'package:flutter/material.dart'; | ||||
| import 'package:provider/provider.dart'; | ||||
|  | ||||
| import '../components/api/APIBuilder.dart'; | ||||
| import '../constants/color_constants.dart'; | ||||
| import '../providers/packages_provider.dart'; | ||||
|  | ||||
| class AurScreen extends StatefulWidget { | ||||
|   const AurScreen({super.key}); | ||||
|  | ||||
|   @override | ||||
|   State<AurScreen> createState() => _AurScreenState(); | ||||
| } | ||||
|  | ||||
| class _AurScreenState extends State<AurScreen> { | ||||
|   TextEditingController controller = TextEditingController(); | ||||
|   String query = ""; | ||||
|   Timer? timer; | ||||
|  | ||||
|   @override | ||||
|   Widget build(BuildContext context) { | ||||
|     return Scaffold( | ||||
|       appBar: AppBar(), | ||||
|       body: MultiProvider( | ||||
|         providers: [ | ||||
|           ChangeNotifierProvider(create: (_) => PackagesProvider()), | ||||
|           ChangeNotifierProvider(create: (_) => AURSearchProvider()) | ||||
|         ], | ||||
|         child: Padding( | ||||
|           padding: const EdgeInsets.all(defaultPadding), | ||||
|           child: Container( | ||||
|             padding: const EdgeInsets.all(defaultPadding), | ||||
|             decoration: const BoxDecoration( | ||||
|               color: secondaryColor, | ||||
|               borderRadius: BorderRadius.all(Radius.circular(10)), | ||||
|             ), | ||||
|             child: SingleChildScrollView( | ||||
|               child: Column( | ||||
|                 crossAxisAlignment: CrossAxisAlignment.start, | ||||
|                 children: [ | ||||
|                   Text( | ||||
|                     "AUR Packages", | ||||
|                     style: Theme.of(context).textTheme.subtitle1, | ||||
|                   ), | ||||
|                   const Text("Search:"), | ||||
|                   TextField( | ||||
|                       controller: controller, | ||||
|                       onChanged: (value) { | ||||
|                         // cancel old timer if active | ||||
|                         timer?.cancel(); | ||||
|                         // schedule new timer | ||||
|                         timer = Timer(const Duration(milliseconds: 300), () { | ||||
|                           setState(() { | ||||
|                             query = value; | ||||
|                           }); | ||||
|                         }); | ||||
|                       }, | ||||
|                       decoration: | ||||
|                           const InputDecoration(hintText: "Type to search...")), | ||||
|                   SizedBox( | ||||
|                     width: double.infinity, | ||||
|                     child: APIBuilder<AURSearchProvider, List<AurPackage>, | ||||
|                             AurSearchDTO>( | ||||
|                         dto: AurSearchDTO(query: query), | ||||
|                         onLoad: () => Center( | ||||
|                               child: Column( | ||||
|                                 children: [ | ||||
|                                   const SizedBox( | ||||
|                                     height: 15, | ||||
|                                   ), | ||||
|                                   query.length < 3 | ||||
|                                       ? const Text( | ||||
|                                           "Type to search for an AUR package") | ||||
|                                       : const Text("loading") | ||||
|                                 ], | ||||
|                               ), | ||||
|                             ), | ||||
|                         onData: (data) => AurSearchTable(data: data)), | ||||
|                   ) | ||||
|                 ], | ||||
|               ), | ||||
|             ), | ||||
|           ), | ||||
|         ), | ||||
|       ), | ||||
|     ); | ||||
|   } | ||||
| } | ||||
| @@ -61,9 +61,11 @@ class _PackageScreenState extends State<PackageScreen> { | ||||
|                           child: ElevatedButton( | ||||
|                             onPressed: () async { | ||||
|                               final confirmResult = | ||||
|                                   await showDeleteConfirmationDialog(context); | ||||
|                               if (!confirmResult) return; | ||||
|  | ||||
|                                   await showConfirmationDialog( | ||||
|                                 context, | ||||
|                                 "Delete Package", | ||||
|                                 "Are you sure to delete this Package?", | ||||
|                                 () async { | ||||
|                                   final succ = await API.deletePackage(pkg.id); | ||||
|                                   if (succ) { | ||||
|                                     context.pop(); | ||||
| @@ -79,6 +81,9 @@ class _PackageScreenState extends State<PackageScreen> { | ||||
|                                         .refresh(context); | ||||
|                                   } | ||||
|                                 }, | ||||
|                                 () {}, | ||||
|                               ); | ||||
|                             }, | ||||
|                             child: const Text( | ||||
|                               "Delete", | ||||
|                               style: TextStyle(color: Colors.redAccent), | ||||
|   | ||||
		Reference in New Issue
	
	Block a user