add page for aur search

install aur package by button click
This commit is contained in:
lukas-heiligenbrunner 2024-01-27 14:51:45 +01:00
parent bb34e56be0
commit c924a151cb
11 changed files with 262 additions and 36 deletions

15
frontend/lib/api/aur.dart Normal file
View 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;
}
}

View File

@ -26,6 +26,15 @@ class _APIBuilderState<T extends BaseProvider, K, DTO>
extends State<APIBuilder<T, K, DTO>> { extends State<APIBuilder<T, K, DTO>> {
Timer? timer; 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 @override
void initState() { void initState() {
super.initState(); super.initState();

View 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;
},
),
),
],
);
}
}

View File

@ -1,6 +1,12 @@
import 'package:flutter/material.dart'; 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>( return (await showDialog<bool>(
context: context, context: context,
barrierDismissible: false, barrierDismissible: false,
@ -17,20 +23,24 @@ Future<bool> showDeleteConfirmationDialog(BuildContext context) async {
), ),
// Delete confirmation dialog // Delete confirmation dialog
AlertDialog( AlertDialog(
title: Text('Confirm Delete'), title: Text(title),
content: Text('Are you sure you want to delete this item?'), content: Text(content),
actions: <Widget>[ actions: <Widget>[
TextButton( TextButton(
onPressed: () { onPressed: () {
Navigator.of(context).pop(true); Navigator.of(context).pop(true);
successCallback();
}, },
child: Text('Yes, Delete'), child: const Text('Yes'),
), ),
TextButton( TextButton(
onPressed: () { onPressed: () {
Navigator.of(context).pop(false); // Dismiss dialog Navigator.of(context).pop(false); // Dismiss dialog
if (errorCallback != null) {
errorCallback();
}
}, },
child: Text('Cancel'), child: const Text('Cancel'),
), ),
], ],
), ),

View File

@ -87,19 +87,18 @@ class PackagesTable extends StatelessWidget {
child: const Text("Delete", child: const Text("Delete",
style: TextStyle(color: Colors.redAccent)), style: TextStyle(color: Colors.redAccent)),
onPressed: () async { onPressed: () async {
final confirmResult = await showConfirmationDialog(context, "Delete Package",
await showDeleteConfirmationDialog(context); "Are you sure to delete this Package?", () async {
if (!confirmResult) return; final succ = await API.deletePackage(package.id);
if (succ) {
final succ = await API.deletePackage(package.id); Provider.of<PackagesProvider>(context, listen: false)
if (succ) { .refresh(context);
Provider.of<PackagesProvider>(context, listen: false) Provider.of<BuildsProvider>(context, listen: false)
.refresh(context); .refresh(context);
Provider.of<BuildsProvider>(context, listen: false) Provider.of<StatsProvider>(context, listen: false)
.refresh(context); .refresh(context);
Provider.of<StatsProvider>(context, listen: false) }
.refresh(context); }, null);
}
}, },
), ),
], ],

View File

@ -1,3 +1,4 @@
import 'package:aurcache/screens/aur_screen.dart';
import 'package:aurcache/screens/build_screen.dart'; import 'package:aurcache/screens/build_screen.dart';
import 'package:aurcache/screens/builds_screen.dart'; import 'package:aurcache/screens/builds_screen.dart';
import 'package:aurcache/screens/dashboard_screen.dart'; import 'package:aurcache/screens/dashboard_screen.dart';
@ -40,6 +41,10 @@ final appRouter = GoRouter(
path: '/packages', path: '/packages',
builder: (context, state) => const PackagesScreen(), builder: (context, state) => const PackagesScreen(),
), ),
GoRoute(
path: '/aur',
builder: (context, state) => AurScreen(),
),
GoRoute( GoRoute(
path: '/package/:id', path: '/package/:id',
builder: (context, state) { builder: (context, state) {

View File

@ -43,12 +43,16 @@ class SideMenu extends StatelessWidget {
DrawerListTile( DrawerListTile(
title: "Builds", title: "Builds",
svgSrc: "assets/icons/menu_tran.svg", svgSrc: "assets/icons/menu_tran.svg",
press: () {}, press: () {
context.go("/builds");
},
), ),
DrawerListTile( DrawerListTile(
title: "AUR", title: "AUR",
svgSrc: "assets/icons/menu_task.svg", svgSrc: "assets/icons/menu_task.svg",
press: () {}, press: () {
context.go("/aur");
},
), ),
DrawerListTile( DrawerListTile(
title: "Settings", title: "Settings",

View 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,
);
}
}

View 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);
}
}

View 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)),
)
],
),
),
),
),
),
);
}
}

View File

@ -61,23 +61,28 @@ class _PackageScreenState extends State<PackageScreen> {
child: ElevatedButton( child: ElevatedButton(
onPressed: () async { onPressed: () async {
final confirmResult = final confirmResult =
await showDeleteConfirmationDialog(context); await showConfirmationDialog(
if (!confirmResult) return; context,
"Delete Package",
"Are you sure to delete this Package?",
() async {
final succ = await API.deletePackage(pkg.id);
if (succ) {
context.pop();
final succ = await API.deletePackage(pkg.id); Provider.of<PackagesProvider>(context,
if (succ) { listen: false)
context.pop(); .refresh(context);
Provider.of<BuildsProvider>(context,
Provider.of<PackagesProvider>(context, listen: false)
listen: false) .refresh(context);
.refresh(context); Provider.of<StatsProvider>(context,
Provider.of<BuildsProvider>(context, listen: false)
listen: false) .refresh(context);
.refresh(context); }
Provider.of<StatsProvider>(context, },
listen: false) () {},
.refresh(context); );
}
}, },
child: const Text( child: const Text(
"Delete", "Delete",