Calling API in LatestReportColumn.dart
and improving UI
This commit is contained in:
@@ -12,6 +12,7 @@ export 'package:ac_web/Api/Dtos/AgentDto.dart';
|
||||
export 'package:ac_web/Api/Dtos/AgentTaskDto.dart';
|
||||
export 'package:ac_web/Api/Dtos/EAgentStatus.dart';
|
||||
export 'package:ac_web/Api/Dtos/EAgentType.dart';
|
||||
export 'package:ac_web/Api/Dtos/EReportType.dart';
|
||||
export 'package:ac_web/Api/Dtos/ReportDto.dart';
|
||||
export 'package:ac_web/Api/Services/AgentsService.dart';
|
||||
export 'package:ac_web/Api/Services/ApiServiceBase.dart';
|
||||
|
||||
@@ -0,0 +1,50 @@
|
||||
/// Mirror of the backend `EReportType` enum.
|
||||
///
|
||||
/// The order of the values has to match the order in the .Net enum, because
|
||||
/// the api may serialize enums as their numeric value. [unknown] is kept last
|
||||
/// so that it never shadows a real backend value and acts as fallback for
|
||||
/// values this client does not know yet.
|
||||
enum EReportType {
|
||||
log,
|
||||
codeReview,
|
||||
projectPlan,
|
||||
research,
|
||||
newspaper,
|
||||
unknown;
|
||||
|
||||
/// Name as it is written in the .Net enum, used for json (de)serialization.
|
||||
String get wireName {
|
||||
switch (this) {
|
||||
case EReportType.log:
|
||||
return 'LOG';
|
||||
case EReportType.codeReview:
|
||||
return 'CODE_REVIEW';
|
||||
case EReportType.projectPlan:
|
||||
return 'PROJECT_PLAN';
|
||||
case EReportType.research:
|
||||
return 'RESEARCH';
|
||||
case EReportType.newspaper:
|
||||
return 'NEWSPAPER';
|
||||
case EReportType.unknown:
|
||||
return 'UNKNOWN';
|
||||
}
|
||||
}
|
||||
|
||||
/// Human readable label, e.g. for chips or table cells.
|
||||
String get displayName {
|
||||
switch (this) {
|
||||
case EReportType.log:
|
||||
return 'Log';
|
||||
case EReportType.codeReview:
|
||||
return 'Code Review';
|
||||
case EReportType.projectPlan:
|
||||
return 'Project Plan';
|
||||
case EReportType.research:
|
||||
return 'Research';
|
||||
case EReportType.newspaper:
|
||||
return 'Newspaper';
|
||||
case EReportType.unknown:
|
||||
return 'Unknown';
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,9 +1,11 @@
|
||||
import 'package:ac_web/Api/Dtos/EReportType.dart';
|
||||
import 'package:ac_web/Api/JsonReader.dart';
|
||||
|
||||
/// Dart counterpart of the backend `ReportDto` returned by `GET /reports`.
|
||||
class ReportDto {
|
||||
const ReportDto({
|
||||
required this.id,
|
||||
required this.title,
|
||||
required this.author,
|
||||
required this.timestamp,
|
||||
required this.type,
|
||||
@@ -11,21 +13,27 @@ class ReportDto {
|
||||
});
|
||||
|
||||
final String id;
|
||||
final String title;
|
||||
final String author;
|
||||
final DateTime timestamp;
|
||||
|
||||
/// Free form report type. The backend sends this as a plain string, so it is
|
||||
/// intentionally not modelled as an enum here.
|
||||
final String type;
|
||||
final EReportType type;
|
||||
|
||||
final String text;
|
||||
|
||||
factory ReportDto.fromJson(Map<String, dynamic> json) {
|
||||
return ReportDto(
|
||||
id: JsonReader.requireString(json, 'Id'),
|
||||
title: JsonReader.optionalString(json, 'Title') ?? '',
|
||||
author: JsonReader.requireString(json, 'Author'),
|
||||
timestamp: JsonReader.requireDateTime(json, 'Timestamp'),
|
||||
type: JsonReader.optionalString(json, 'Type') ?? '',
|
||||
type: JsonReader.enumValue(
|
||||
json,
|
||||
'Type',
|
||||
EReportType.values,
|
||||
(value) => value.wireName,
|
||||
EReportType.unknown,
|
||||
),
|
||||
text: JsonReader.optionalString(json, 'Text') ?? '',
|
||||
);
|
||||
}
|
||||
@@ -33,13 +41,15 @@ class ReportDto {
|
||||
Map<String, dynamic> toJson() {
|
||||
return <String, dynamic>{
|
||||
'Id': id,
|
||||
'Title': title,
|
||||
'Author': author,
|
||||
'Timestamp': timestamp.toIso8601String(),
|
||||
'Type': type,
|
||||
'Type': type.wireName,
|
||||
'Text': text,
|
||||
};
|
||||
}
|
||||
|
||||
@override
|
||||
String toString() => 'ReportDto(id: $id, author: $author, type: $type)';
|
||||
String toString() =>
|
||||
'ReportDto(id: $id, title: $title, author: $author, type: ${type.wireName})';
|
||||
}
|
||||
|
||||
@@ -1,49 +1,136 @@
|
||||
import 'package:ac_web/Api/AcApi.dart';
|
||||
import 'package:ac_web/Api/Dtos/EReportType.dart';
|
||||
import 'package:ac_web/Api/Dtos/ReportDto.dart';
|
||||
import 'package:ac_web/Widgets/LatestReportEntry.dart';
|
||||
import 'package:ac_web/Widgets/LatestReportHeader.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
|
||||
class LatestReportColumn extends StatelessWidget{
|
||||
class LatestReportColumn extends StatefulWidget{
|
||||
const LatestReportColumn({super.key});
|
||||
|
||||
@override
|
||||
State<LatestReportColumn> createState() => _LatestReportColumnState();
|
||||
}
|
||||
|
||||
class _LatestReportColumnState extends State<LatestReportColumn>{
|
||||
|
||||
late Future<List<ReportDto>> mReportsFuture;
|
||||
|
||||
@override
|
||||
void initState() {
|
||||
super.initState();
|
||||
mReportsFuture = AcApi.instance.reports.getReports();
|
||||
}
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return FutureBuilder<List<ReportDto>>(
|
||||
future: mReportsFuture,
|
||||
builder: (context, snapshot) {
|
||||
|
||||
if (snapshot.connectionState != ConnectionState.done) {
|
||||
return const Center(child: CircularProgressIndicator());
|
||||
}
|
||||
|
||||
if (snapshot.hasError) {
|
||||
return Center(child: Text('Could not load reports: ${snapshot.error}'));
|
||||
}
|
||||
|
||||
// The list of reports fetched from the ReportsController.
|
||||
final List<ReportDto> reports = snapshot.data ?? <ReportDto>[];
|
||||
|
||||
return _buildList(reports);
|
||||
},
|
||||
);
|
||||
}
|
||||
|
||||
Widget _buildList(List<ReportDto> reports) {
|
||||
|
||||
List<Widget> widgets = <Widget>[];
|
||||
|
||||
// Title Entry
|
||||
widgets.add(LatestReportHeader(text: "Latest Reports", bigHeader: true,));
|
||||
|
||||
widgets.add(
|
||||
LatestReportHeader(text: "Today")
|
||||
);
|
||||
DateTime? lastDay;
|
||||
|
||||
widgets.add(LatestReportEntry(
|
||||
text: "Entry 0",
|
||||
icon: Icons.code,
|
||||
onTap: () => {widgets.add(
|
||||
LatestReportHeader(text: "WOOOD")
|
||||
)},
|
||||
));
|
||||
widgets.add(LatestReportEntry(
|
||||
text: "Entry 1",
|
||||
icon: Icons.newspaper,
|
||||
));
|
||||
// Iterate through the reports and create Report entries accordingly
|
||||
for(int i = 0; i < reports.length; i++){
|
||||
|
||||
widgets.add(
|
||||
LatestReportHeader(text: "Yesterday")
|
||||
);
|
||||
widgets.add(LatestReportEntry(
|
||||
text: "Entry -1",
|
||||
icon: Icons.code,
|
||||
));
|
||||
// Add a day header above the first entry of every day
|
||||
final DateTime day = _dayOf(reports[i].timestamp);
|
||||
|
||||
widgets.add(
|
||||
LatestReportHeader(text: "15. August")
|
||||
);
|
||||
if(lastDay == null || day != lastDay){
|
||||
widgets.add(LatestReportHeader(text: _dayLabel(day)));
|
||||
lastDay = day;
|
||||
}
|
||||
|
||||
IconData icon;
|
||||
|
||||
switch(reports[i].type){
|
||||
|
||||
case EReportType.log:
|
||||
icon = Icons.receipt_long;
|
||||
case EReportType.codeReview:
|
||||
icon = Icons.code;
|
||||
case EReportType.projectPlan:
|
||||
icon = Icons.assignment;
|
||||
case EReportType.research:
|
||||
icon = Icons.science;
|
||||
case EReportType.newspaper:
|
||||
icon = Icons.newspaper;
|
||||
case EReportType.unknown:
|
||||
icon = Icons.question_mark;
|
||||
}
|
||||
|
||||
widgets.add(LatestReportEntry(
|
||||
text: reports[i].title,
|
||||
icon: icon,
|
||||
onTap: () => {widgets.add(
|
||||
LatestReportHeader(text: "WOOOD")
|
||||
)},
|
||||
));
|
||||
}
|
||||
|
||||
return ListView(
|
||||
padding: EdgeInsetsGeometry.all(8),
|
||||
children: widgets,
|
||||
);
|
||||
}
|
||||
|
||||
/// Strips the time part, so that two timestamps of the same day are equal.
|
||||
DateTime _dayOf(DateTime timestamp) {
|
||||
final DateTime local = timestamp.toLocal();
|
||||
return DateTime(local.year, local.month, local.day);
|
||||
}
|
||||
|
||||
/// "Today", "Yesterday" or e.g. "15. August" for all other days.
|
||||
String _dayLabel(DateTime day) {
|
||||
final DateTime today = _dayOf(DateTime.now());
|
||||
final DateTime yesterday = DateTime(today.year, today.month, today.day - 1);
|
||||
|
||||
if (day == today) {
|
||||
return "Today";
|
||||
}
|
||||
|
||||
if (day == yesterday) {
|
||||
return "Yesterday";
|
||||
}
|
||||
|
||||
return "${day.day}. ${_monthNames[day.month - 1]}";
|
||||
}
|
||||
|
||||
static const List<String> _monthNames = <String>[
|
||||
"January",
|
||||
"February",
|
||||
"March",
|
||||
"April",
|
||||
"May",
|
||||
"June",
|
||||
"July",
|
||||
"August",
|
||||
"September",
|
||||
"October",
|
||||
"November",
|
||||
"December",
|
||||
];
|
||||
}
|
||||
@@ -98,14 +98,14 @@ void main() {
|
||||
'Id': 'older',
|
||||
'Author': 'Ada',
|
||||
'Timestamp': '2026-08-20T10:00:00Z',
|
||||
'Type': 'Info',
|
||||
'Type': 'LOG',
|
||||
'Text': 'first',
|
||||
},
|
||||
{
|
||||
'Id': 'newer',
|
||||
'Author': 'Grace',
|
||||
'Timestamp': '2026-08-27T10:00:00Z',
|
||||
'Type': 'Warning',
|
||||
'Type': 'CODE_REVIEW',
|
||||
'Text': 'second',
|
||||
},
|
||||
]),
|
||||
@@ -115,7 +115,7 @@ void main() {
|
||||
|
||||
expect(reports.map((report) => report.id), ['newer', 'older']);
|
||||
expect(reports.first.author, 'Grace');
|
||||
expect(reports.first.type, 'Warning');
|
||||
expect(reports.first.type, EReportType.codeReview);
|
||||
});
|
||||
|
||||
test('maps error status codes onto ApiHttpException', () async {
|
||||
|
||||
Reference in New Issue
Block a user