feat: add swipe action to refresh report screen
This commit is contained in:
parent
ebc4cdf754
commit
055b402c81
1 changed files with 21 additions and 117 deletions
|
|
@ -1,7 +1,6 @@
|
||||||
import 'dart:developer';
|
import 'dart:developer';
|
||||||
|
|
||||||
import 'package:fl_chart/fl_chart.dart';
|
import 'package:fl_chart/fl_chart.dart';
|
||||||
import 'package:flutter/cupertino.dart';
|
|
||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
import 'package:flutter_platform_widgets/flutter_platform_widgets.dart';
|
import 'package:flutter_platform_widgets/flutter_platform_widgets.dart';
|
||||||
import 'package:flutter_slidable/flutter_slidable.dart';
|
import 'package:flutter_slidable/flutter_slidable.dart';
|
||||||
|
|
@ -16,14 +15,12 @@ DateTime unixSecondsToDateTime(int ts) =>
|
||||||
DateTime.fromMillisecondsSinceEpoch(ts * 1000, isUtc: true).toLocal();
|
DateTime.fromMillisecondsSinceEpoch(ts * 1000, isUtc: true).toLocal();
|
||||||
|
|
||||||
extension TimeEntryFormatting on TimeEntry {
|
extension TimeEntryFormatting on TimeEntry {
|
||||||
/// Konvertiert den startTime (Unix Timestamp) in ein lokales DateTime Objekt.
|
|
||||||
DateTime get startDateTime =>
|
DateTime get startDateTime =>
|
||||||
DateTime.fromMillisecondsSinceEpoch(
|
DateTime.fromMillisecondsSinceEpoch(
|
||||||
startTime.toInt() * 1000,
|
startTime.toInt() * 1000,
|
||||||
isUtc: true,
|
isUtc: true,
|
||||||
).toLocal();
|
).toLocal();
|
||||||
|
|
||||||
/// Konvertiert den optionalen endTime (Unix Timestamp) in ein lokales DateTime Objekt.
|
|
||||||
DateTime? get endDateTime =>
|
DateTime? get endDateTime =>
|
||||||
endTime == null
|
endTime == null
|
||||||
? null
|
? null
|
||||||
|
|
@ -32,7 +29,6 @@ extension TimeEntryFormatting on TimeEntry {
|
||||||
isUtc: true,
|
isUtc: true,
|
||||||
).toLocal();
|
).toLocal();
|
||||||
|
|
||||||
/// Formatiert die Dauer (durationSecs) als HH:MM:SS String.
|
|
||||||
String get durationFormatted {
|
String get durationFormatted {
|
||||||
final durationInSeconds = durationSecs?.toInt();
|
final durationInSeconds = durationSecs?.toInt();
|
||||||
if (durationInSeconds == null || durationInSeconds < 0) return '--:--:--';
|
if (durationInSeconds == null || durationInSeconds < 0) return '--:--:--';
|
||||||
|
|
@ -47,7 +43,6 @@ extension TimeEntryFormatting on TimeEntry {
|
||||||
}
|
}
|
||||||
|
|
||||||
extension ReportDataFormatting on ReportData {
|
extension ReportDataFormatting on ReportData {
|
||||||
/// Formatiert die Gesamtdauer als HH:MM:SS String.
|
|
||||||
String get totalDurationFormatted {
|
String get totalDurationFormatted {
|
||||||
final durationInSeconds = totalDurationSecs.toInt();
|
final durationInSeconds = totalDurationSecs.toInt();
|
||||||
if (durationInSeconds < 0) return '--:--:--';
|
if (durationInSeconds < 0) return '--:--:--';
|
||||||
|
|
@ -99,8 +94,9 @@ class _ReportScreenState extends State<ReportScreen> {
|
||||||
final Map<DateTime, Map<String, Duration>> dailyTagDurations = {};
|
final Map<DateTime, Map<String, Duration>> dailyTagDurations = {};
|
||||||
|
|
||||||
for (final entry in entries) {
|
for (final entry in entries) {
|
||||||
if (entry.durationSecs == null || entry.durationSecs!.toInt() <= 0)
|
if (entry.durationSecs == null || entry.durationSecs!.toInt() <= 0) {
|
||||||
continue;
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
final entryDay = DateTime(
|
final entryDay = DateTime(
|
||||||
entry.startDateTime.year,
|
entry.startDateTime.year,
|
||||||
|
|
@ -393,14 +389,24 @@ class _ReportScreenState extends State<ReportScreen> {
|
||||||
children: [
|
children: [
|
||||||
_buildFilterControls(tagOptions),
|
_buildFilterControls(tagOptions),
|
||||||
Expanded(
|
Expanded(
|
||||||
child:
|
child: RefreshIndicator.adaptive(
|
||||||
_isLoadingReport
|
onRefresh: _generateReport,
|
||||||
? Center(child: PlatformCircularProgressIndicator())
|
child:
|
||||||
: _reportData == null
|
_isLoadingReport
|
||||||
? const Center(
|
? Center(child: PlatformCircularProgressIndicator())
|
||||||
child: Text('Keine Reportdaten gefunden oder Fehler.'),
|
: _reportData == null
|
||||||
)
|
? ListView(
|
||||||
: _buildReportView(),
|
children: [
|
||||||
|
SizedBox(
|
||||||
|
height: MediaQuery.of(context).size.height * 0.5,
|
||||||
|
child: Text(
|
||||||
|
'Keine Reportdaten gefunden oder Fehler.',
|
||||||
|
),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
)
|
||||||
|
: _buildReportView(),
|
||||||
|
),
|
||||||
),
|
),
|
||||||
],
|
],
|
||||||
),
|
),
|
||||||
|
|
@ -487,8 +493,6 @@ class _ReportScreenState extends State<ReportScreen> {
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
// SizedBox(height: 250, child: _buildPieChart(_reportData!)),
|
|
||||||
// const Divider(),
|
|
||||||
SizedBox(
|
SizedBox(
|
||||||
height: 250,
|
height: 250,
|
||||||
child: _buildBarChart(_reportData!, startDate, endDate),
|
child: _buildBarChart(_reportData!, startDate, endDate),
|
||||||
|
|
@ -512,103 +516,6 @@ class _ReportScreenState extends State<ReportScreen> {
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
Widget _buildPieChart(ReportData data) {
|
|
||||||
Map<String, double> durationPerTag = {};
|
|
||||||
for (var entry in data.entries) {
|
|
||||||
final tagName = entry.tagName ?? 'Ohne Tag';
|
|
||||||
final duration = (entry.durationSecs?.toInt() ?? 0).toDouble();
|
|
||||||
durationPerTag[tagName] = (durationPerTag[tagName] ?? 0) + duration;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (durationPerTag.isEmpty) {
|
|
||||||
return const Center(child: Text("Keine Daten für Chart"));
|
|
||||||
}
|
|
||||||
|
|
||||||
List<Color> colors = Colors.primaries.take(durationPerTag.length).toList();
|
|
||||||
if (durationPerTag.length > Colors.primaries.length) {
|
|
||||||
colors.addAll(
|
|
||||||
Colors.accents.take(durationPerTag.length - Colors.primaries.length),
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
int colorIndex = 0;
|
|
||||||
List<PieChartSectionData> sections =
|
|
||||||
durationPerTag.entries.map((entry) {
|
|
||||||
final isTouched = false;
|
|
||||||
final fontSize = isTouched ? 18.0 : 14.0;
|
|
||||||
final radius = isTouched ? 60.0 : 50.0;
|
|
||||||
final color = colors[colorIndex % colors.length];
|
|
||||||
colorIndex++;
|
|
||||||
|
|
||||||
final hours = (entry.value / 3600).toStringAsFixed(1);
|
|
||||||
|
|
||||||
return PieChartSectionData(
|
|
||||||
color: color,
|
|
||||||
value: entry.value,
|
|
||||||
title: '${entry.key}\n${hours}h',
|
|
||||||
radius: radius,
|
|
||||||
titleStyle: TextStyle(
|
|
||||||
fontSize: fontSize,
|
|
||||||
fontWeight: FontWeight.bold,
|
|
||||||
color: Colors.white,
|
|
||||||
shadows: [
|
|
||||||
Shadow(color: Colors.black.withOpacity(0.7), blurRadius: 2),
|
|
||||||
],
|
|
||||||
),
|
|
||||||
titlePositionPercentageOffset: 0.6,
|
|
||||||
);
|
|
||||||
}).toList();
|
|
||||||
|
|
||||||
return Padding(
|
|
||||||
padding: const EdgeInsets.all(8.0),
|
|
||||||
child: PieChart(
|
|
||||||
PieChartData(
|
|
||||||
sections: sections,
|
|
||||||
centerSpaceRadius: 40,
|
|
||||||
sectionsSpace: 2,
|
|
||||||
),
|
|
||||||
duration: const Duration(milliseconds: 150),
|
|
||||||
curve: Curves.linear,
|
|
||||||
),
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
// Widget _buildDataTable(List<TimeEntry> entries) {
|
|
||||||
// final DateFormat timeFormatter = DateFormat('HH:mm:ss');
|
|
||||||
// final DateFormat dateFormatter = DateFormat('dd.MM.yy');
|
|
||||||
|
|
||||||
// return DataTable(
|
|
||||||
// columnSpacing: 10,
|
|
||||||
// columns: [
|
|
||||||
// DataColumn(label: PlatformText('Tag')),
|
|
||||||
// DataColumn(label: PlatformText('Start')),
|
|
||||||
// DataColumn(label: PlatformText('Ende')),
|
|
||||||
// DataColumn(label: PlatformText('Dauer'), numeric: true),
|
|
||||||
// ],
|
|
||||||
// rows:
|
|
||||||
// entries.map((entry) {
|
|
||||||
// return DataRow(
|
|
||||||
// cells: [
|
|
||||||
// DataCell(PlatformText(entry.tagName ?? '-')),
|
|
||||||
// DataCell(
|
|
||||||
// PlatformText(
|
|
||||||
// '${dateFormatter.format(unixSecondsToDateTime(entry.startTime))}\n${timeFormatter.format(unixSecondsToDateTime(entry.startTime))}',
|
|
||||||
// ),
|
|
||||||
// ),
|
|
||||||
// DataCell(
|
|
||||||
// entry.endTime != null
|
|
||||||
// ? PlatformText(
|
|
||||||
// '${dateFormatter.format(unixSecondsToDateTime(entry.endTime!))}\n${timeFormatter.format(unixSecondsToDateTime(entry.endTime!))}',
|
|
||||||
// )
|
|
||||||
// : PlatformText('-'),
|
|
||||||
// ),
|
|
||||||
// DataCell(Text(entry.durationFormatted)),
|
|
||||||
// ],
|
|
||||||
// );
|
|
||||||
// }).toList(),
|
|
||||||
// );
|
|
||||||
// }
|
|
||||||
|
|
||||||
void _confirmAndDeleteEntry(TimeEntry entry, BuildContext context) {
|
void _confirmAndDeleteEntry(TimeEntry entry, BuildContext context) {
|
||||||
showPlatformDialog(
|
showPlatformDialog(
|
||||||
context: context,
|
context: context,
|
||||||
|
|
@ -855,10 +762,7 @@ class _ReportScreenState extends State<ReportScreen> {
|
||||||
actions: <Widget>[
|
actions: <Widget>[
|
||||||
PlatformDialogAction(
|
PlatformDialogAction(
|
||||||
child: PlatformText('OK'),
|
child: PlatformText('OK'),
|
||||||
onPressed:
|
onPressed: () => Navigator.pop(dialogContext),
|
||||||
() => Navigator.pop(
|
|
||||||
dialogContext,
|
|
||||||
), // Pop using dialog's context
|
|
||||||
),
|
),
|
||||||
],
|
],
|
||||||
),
|
),
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue