add apibuilder widget to make api callsmore straightforward and data updateable from everywhere through providers
This commit is contained in:
@ -1,7 +1,10 @@
|
||||
import 'dart:async';
|
||||
|
||||
import 'package:aurcache/api/builds.dart';
|
||||
import 'package:aurcache/components/build_output.dart';
|
||||
import 'package:aurcache/models/build.dart';
|
||||
import 'package:aurcache/providers/APIBuilder.dart';
|
||||
import 'package:aurcache/providers/build_provider.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:go_router/go_router.dart';
|
||||
|
||||
@ -18,146 +21,54 @@ class BuildScreen extends StatefulWidget {
|
||||
}
|
||||
|
||||
class _BuildScreenState extends State<BuildScreen> {
|
||||
late Future<Build> buildData;
|
||||
late Future<String> initialOutput;
|
||||
|
||||
String output = "";
|
||||
Timer? outputTimer, buildDataTimer;
|
||||
final scrollController = ScrollController();
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return Scaffold(
|
||||
body: FutureBuilder(
|
||||
future: buildData,
|
||||
builder: (context, snapshot) {
|
||||
if (snapshot.hasData) {
|
||||
final buildData = snapshot.data!;
|
||||
|
||||
return Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
mainAxisAlignment: MainAxisAlignment.start,
|
||||
children: [
|
||||
Row(
|
||||
mainAxisAlignment: MainAxisAlignment.start,
|
||||
children: [
|
||||
const SizedBox(
|
||||
width: 10,
|
||||
),
|
||||
IconButton(
|
||||
icon: Icon(
|
||||
switchSuccessIcon(buildData.status),
|
||||
color: switchSuccessColor(buildData.status),
|
||||
),
|
||||
onPressed: () {
|
||||
context.replace("/build/${buildData.id}");
|
||||
},
|
||||
),
|
||||
const SizedBox(
|
||||
width: 10,
|
||||
),
|
||||
Text(
|
||||
buildData.pkg_name,
|
||||
style: const TextStyle(fontWeight: FontWeight.bold),
|
||||
),
|
||||
const SizedBox(
|
||||
width: 10,
|
||||
),
|
||||
const Text("triggered 2 months ago")
|
||||
],
|
||||
),
|
||||
const SizedBox(
|
||||
height: 15,
|
||||
),
|
||||
Expanded(
|
||||
flex: 1,
|
||||
child: SingleChildScrollView(
|
||||
controller: scrollController,
|
||||
scrollDirection: Axis.vertical, //.horizontal
|
||||
child: Padding(
|
||||
padding: const EdgeInsets.only(left: 30, right: 15),
|
||||
child: Text(
|
||||
output,
|
||||
style: const TextStyle(
|
||||
fontSize: 16.0,
|
||||
color: Colors.white,
|
||||
),
|
||||
),
|
||||
),
|
||||
body: APIBuilder<BuildProvider, Build, BuildDTO>(
|
||||
dto: BuildDTO(buildID: widget.buildID),
|
||||
interval: const Duration(seconds: 10),
|
||||
onLoad: () => const Text("no data"),
|
||||
onData: (buildData) {
|
||||
return Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
mainAxisAlignment: MainAxisAlignment.start,
|
||||
children: [
|
||||
Row(
|
||||
mainAxisAlignment: MainAxisAlignment.start,
|
||||
children: [
|
||||
const SizedBox(
|
||||
width: 10,
|
||||
),
|
||||
),
|
||||
],
|
||||
);
|
||||
} else {
|
||||
return const Text("loading build");
|
||||
}
|
||||
IconButton(
|
||||
icon: Icon(
|
||||
switchSuccessIcon(buildData.status),
|
||||
color: switchSuccessColor(buildData.status),
|
||||
),
|
||||
onPressed: () {
|
||||
context.replace("/build/${buildData.id}");
|
||||
},
|
||||
),
|
||||
const SizedBox(
|
||||
width: 10,
|
||||
),
|
||||
Text(
|
||||
buildData.pkg_name,
|
||||
style: const TextStyle(fontWeight: FontWeight.bold),
|
||||
),
|
||||
const SizedBox(
|
||||
width: 10,
|
||||
),
|
||||
const Text("triggered 2 months ago")
|
||||
],
|
||||
),
|
||||
const SizedBox(
|
||||
height: 15,
|
||||
),
|
||||
BuildOutput(build: buildData)
|
||||
],
|
||||
);
|
||||
}),
|
||||
appBar: AppBar(),
|
||||
);
|
||||
}
|
||||
|
||||
@override
|
||||
void initState() {
|
||||
super.initState();
|
||||
|
||||
initBuildDataLoader();
|
||||
initOutputLoader();
|
||||
}
|
||||
|
||||
void initBuildDataLoader() {
|
||||
buildData = API.getBuild(widget.buildID);
|
||||
buildDataTimer = Timer.periodic(const Duration(seconds: 10), (t) {
|
||||
setState(() {
|
||||
buildData = API.getBuild(widget.buildID);
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
void initOutputLoader() {
|
||||
initialOutput = API.getOutput(buildID: widget.buildID);
|
||||
initialOutput.then((value) {
|
||||
setState(() {
|
||||
output = value;
|
||||
});
|
||||
_scrollToBottom();
|
||||
});
|
||||
|
||||
buildData.then((value) {
|
||||
// poll new output only if not finished
|
||||
if (value.status == 0) {
|
||||
outputTimer =
|
||||
Timer.periodic(const Duration(seconds: 3), (Timer t) async {
|
||||
print("refreshing output");
|
||||
final value = await API.getOutput(
|
||||
buildID: widget.buildID, line: output.split("\n").length);
|
||||
setState(() {
|
||||
output += value;
|
||||
});
|
||||
|
||||
_scrollToBottom();
|
||||
});
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
void _scrollToBottom() {
|
||||
WidgetsBinding.instance.addPostFrameCallback((_) {
|
||||
// scroll to bottom
|
||||
final scrollPosition = scrollController.position;
|
||||
if (scrollPosition.viewportDimension < scrollPosition.maxScrollExtent) {
|
||||
scrollController.animateTo(
|
||||
scrollPosition.maxScrollExtent,
|
||||
duration: const Duration(milliseconds: 200),
|
||||
curve: Curves.easeOut,
|
||||
);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
@override
|
||||
void dispose() {
|
||||
super.dispose();
|
||||
outputTimer?.cancel();
|
||||
buildDataTimer?.cancel();
|
||||
}
|
||||
}
|
||||
|
47
frontend/lib/screens/builds_screen.dart
Normal file
47
frontend/lib/screens/builds_screen.dart
Normal file
@ -0,0 +1,47 @@
|
||||
import 'package:aurcache/components/builds_table.dart';
|
||||
import 'package:aurcache/providers/APIBuilder.dart';
|
||||
import 'package:aurcache/providers/builds_provider.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import '../constants/color_constants.dart';
|
||||
import '../models/build.dart';
|
||||
|
||||
class BuildsScreen extends StatelessWidget {
|
||||
const BuildsScreen({super.key});
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return Scaffold(
|
||||
appBar: AppBar(),
|
||||
body: 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(
|
||||
"All Builds",
|
||||
style: Theme.of(context).textTheme.subtitle1,
|
||||
),
|
||||
SizedBox(
|
||||
width: double.infinity,
|
||||
child: APIBuilder<BuildsProvider, List<Build>, Object>(
|
||||
interval: const Duration(seconds: 10),
|
||||
onLoad: () => const Text("no data"),
|
||||
onData: (data) {
|
||||
return BuildsTable(data: data);
|
||||
}),
|
||||
)
|
||||
],
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
@ -1,5 +1,8 @@
|
||||
import 'package:aurcache/api/statistics.dart';
|
||||
import 'package:aurcache/providers/APIBuilder.dart';
|
||||
import 'package:aurcache/providers/stats_provider.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:provider/provider.dart';
|
||||
|
||||
import '../api/API.dart';
|
||||
import '../components/dashboard/header.dart';
|
||||
@ -11,70 +14,70 @@ import '../components/dashboard/recent_builds.dart';
|
||||
import '../components/dashboard/your_packages.dart';
|
||||
import '../components/dashboard/side_panel.dart';
|
||||
|
||||
class DashboardScreen extends StatelessWidget {
|
||||
final stats = API.listStats();
|
||||
class DashboardScreen extends StatefulWidget {
|
||||
@override
|
||||
State<DashboardScreen> createState() => _DashboardScreenState();
|
||||
}
|
||||
|
||||
class _DashboardScreenState extends State<DashboardScreen> {
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return FutureBuilder(
|
||||
future: stats,
|
||||
builder: (BuildContext context, AsyncSnapshot<dynamic> snapshot) {
|
||||
if (snapshot.hasData) {
|
||||
final Stats stats = snapshot.data!;
|
||||
|
||||
return SafeArea(
|
||||
child: SingleChildScrollView(
|
||||
child: Container(
|
||||
padding: const EdgeInsets.all(defaultPadding),
|
||||
child: Column(
|
||||
return APIBuilder<StatsProvider, Stats, Object>(
|
||||
onData: (stats) {
|
||||
return SafeArea(
|
||||
child: SingleChildScrollView(
|
||||
child: Container(
|
||||
padding: const EdgeInsets.all(defaultPadding),
|
||||
child: Column(
|
||||
children: [
|
||||
const Header(),
|
||||
const SizedBox(height: defaultPadding),
|
||||
QuickInfoBanner(
|
||||
stats: stats,
|
||||
),
|
||||
const SizedBox(height: defaultPadding),
|
||||
Row(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
const Header(),
|
||||
const SizedBox(height: defaultPadding),
|
||||
QuickInfoBanner(
|
||||
stats: stats,
|
||||
),
|
||||
const SizedBox(height: defaultPadding),
|
||||
Row(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
Expanded(
|
||||
flex: 5,
|
||||
child: Column(
|
||||
children: [
|
||||
const YourPackages(),
|
||||
const SizedBox(height: defaultPadding),
|
||||
const RecentBuilds(),
|
||||
if (Responsive.isMobile(context))
|
||||
const SizedBox(height: defaultPadding),
|
||||
if (Responsive.isMobile(context))
|
||||
SidePanel(
|
||||
nrbuilds: stats.total_builds,
|
||||
nrfailedbuilds: stats.failed_builds,
|
||||
nrActiveBuilds: stats.active_builds),
|
||||
],
|
||||
),
|
||||
),
|
||||
if (!Responsive.isMobile(context))
|
||||
const SizedBox(width: defaultPadding),
|
||||
// On Mobile means if the screen is less than 850 we dont want to show it
|
||||
if (!Responsive.isMobile(context))
|
||||
Expanded(
|
||||
flex: 2,
|
||||
child: SidePanel(
|
||||
Expanded(
|
||||
flex: 5,
|
||||
child: Column(
|
||||
children: [
|
||||
const YourPackages(),
|
||||
const SizedBox(height: defaultPadding),
|
||||
const RecentBuilds(),
|
||||
if (Responsive.isMobile(context))
|
||||
const SizedBox(height: defaultPadding),
|
||||
if (Responsive.isMobile(context))
|
||||
SidePanel(
|
||||
nrbuilds: stats.total_builds,
|
||||
nrfailedbuilds: stats.failed_builds,
|
||||
nrActiveBuilds: stats.active_builds),
|
||||
),
|
||||
],
|
||||
)
|
||||
],
|
||||
),
|
||||
),
|
||||
if (!Responsive.isMobile(context))
|
||||
const SizedBox(width: defaultPadding),
|
||||
// On Mobile means if the screen is less than 850 we dont want to show it
|
||||
if (!Responsive.isMobile(context))
|
||||
Expanded(
|
||||
flex: 2,
|
||||
child: SidePanel(
|
||||
nrbuilds: stats.total_builds,
|
||||
nrfailedbuilds: stats.failed_builds,
|
||||
nrActiveBuilds: stats.active_builds),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
)
|
||||
],
|
||||
),
|
||||
);
|
||||
} else {
|
||||
return const Text("loading");
|
||||
}
|
||||
});
|
||||
),
|
||||
),
|
||||
);
|
||||
},
|
||||
onLoad: () {
|
||||
return Text("loading");
|
||||
},
|
||||
);
|
||||
}
|
||||
}
|
||||
|
114
frontend/lib/screens/package_screen.dart
Normal file
114
frontend/lib/screens/package_screen.dart
Normal file
@ -0,0 +1,114 @@
|
||||
import 'dart:async';
|
||||
|
||||
import 'package:aurcache/api/builds.dart';
|
||||
import 'package:aurcache/api/packages.dart';
|
||||
import 'package:aurcache/providers/APIBuilder.dart';
|
||||
import 'package:aurcache/providers/builds_provider.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:go_router/go_router.dart';
|
||||
|
||||
import '../api/API.dart';
|
||||
import '../components/builds_table.dart';
|
||||
import '../components/confirm_popup.dart';
|
||||
import '../constants/color_constants.dart';
|
||||
import '../models/build.dart';
|
||||
import '../models/package.dart';
|
||||
import '../providers/package_provider.dart';
|
||||
import '../providers/packages_provider.dart';
|
||||
|
||||
class PackageScreen extends StatefulWidget {
|
||||
const PackageScreen({super.key, required this.pkgID});
|
||||
|
||||
final int pkgID;
|
||||
|
||||
@override
|
||||
State<PackageScreen> createState() => _PackageScreenState();
|
||||
}
|
||||
|
||||
class _PackageScreenState extends State<PackageScreen> {
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return Scaffold(
|
||||
appBar: AppBar(),
|
||||
body: APIBuilder<PackageProvider, Package, PackageDTO>(
|
||||
dto: PackageDTO(pkgID: widget.pkgID),
|
||||
onLoad: () => const Text("loading"),
|
||||
onData: (pkg) {
|
||||
return Padding(
|
||||
padding: const EdgeInsets.all(defaultPadding),
|
||||
child: Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
Row(
|
||||
mainAxisAlignment: MainAxisAlignment.spaceBetween,
|
||||
crossAxisAlignment: CrossAxisAlignment.center,
|
||||
children: [
|
||||
Container(
|
||||
margin: const EdgeInsets.only(left: 15),
|
||||
child: Text(
|
||||
pkg.name,
|
||||
style: const TextStyle(fontSize: 32),
|
||||
),
|
||||
),
|
||||
Container(
|
||||
margin: const EdgeInsets.only(right: 15),
|
||||
child: ElevatedButton(
|
||||
onPressed: () async {
|
||||
final confirmResult =
|
||||
await showDeleteConfirmationDialog(context);
|
||||
if (!confirmResult) return;
|
||||
|
||||
final succ = await API.deletePackage(pkg.id);
|
||||
if (succ) {
|
||||
context.pop();
|
||||
}
|
||||
},
|
||||
child: const Text(
|
||||
"Delete",
|
||||
style: TextStyle(color: Colors.redAccent),
|
||||
),
|
||||
),
|
||||
)
|
||||
],
|
||||
),
|
||||
const SizedBox(
|
||||
height: 25,
|
||||
),
|
||||
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(
|
||||
"Builds of ${pkg.name}",
|
||||
style: Theme.of(context).textTheme.subtitle1,
|
||||
),
|
||||
SizedBox(
|
||||
width: double.infinity,
|
||||
child: APIBuilder<BuildsProvider, List<Build>,
|
||||
BuildsDTO>(
|
||||
key: GlobalKey(),
|
||||
dto: BuildsDTO(pkgID: pkg.id),
|
||||
interval: const Duration(seconds: 5),
|
||||
onData: (data) {
|
||||
return BuildsTable(data: data);
|
||||
},
|
||||
onLoad: () => const Text("no data"),
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
)
|
||||
],
|
||||
),
|
||||
);
|
||||
}),
|
||||
);
|
||||
}
|
||||
}
|
Reference in New Issue
Block a user