add apibuilder widget to make api callsmore straightforward and data updateable from everywhere through providers

This commit is contained in:
2023-12-30 22:23:42 +01:00
parent 6ca462e2d2
commit fca5df4c70
28 changed files with 812 additions and 341 deletions

View File

@ -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();
}
}

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

View File

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

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