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 'package:fl_chart/fl_chart.dart';
|
||||
import 'package:flutter/cupertino.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter_platform_widgets/flutter_platform_widgets.dart';
|
||||
import 'package:flutter_slidable/flutter_slidable.dart';
|
||||
|
|
@ -16,14 +15,12 @@ DateTime unixSecondsToDateTime(int ts) =>
|
|||
DateTime.fromMillisecondsSinceEpoch(ts * 1000, isUtc: true).toLocal();
|
||||
|
||||
extension TimeEntryFormatting on TimeEntry {
|
||||
/// Konvertiert den startTime (Unix Timestamp) in ein lokales DateTime Objekt.
|
||||
DateTime get startDateTime =>
|
||||
DateTime.fromMillisecondsSinceEpoch(
|
||||
startTime.toInt() * 1000,
|
||||
isUtc: true,
|
||||
).toLocal();
|
||||
|
||||
/// Konvertiert den optionalen endTime (Unix Timestamp) in ein lokales DateTime Objekt.
|
||||
DateTime? get endDateTime =>
|
||||
endTime == null
|
||||
? null
|
||||
|
|
@ -32,7 +29,6 @@ extension TimeEntryFormatting on TimeEntry {
|
|||
isUtc: true,
|
||||
).toLocal();
|
||||
|
||||
/// Formatiert die Dauer (durationSecs) als HH:MM:SS String.
|
||||
String get durationFormatted {
|
||||
final durationInSeconds = durationSecs?.toInt();
|
||||
if (durationInSeconds == null || durationInSeconds < 0) return '--:--:--';
|
||||
|
|
@ -47,7 +43,6 @@ extension TimeEntryFormatting on TimeEntry {
|
|||
}
|
||||
|
||||
extension ReportDataFormatting on ReportData {
|
||||
/// Formatiert die Gesamtdauer als HH:MM:SS String.
|
||||
String get totalDurationFormatted {
|
||||
final durationInSeconds = totalDurationSecs.toInt();
|
||||
if (durationInSeconds < 0) return '--:--:--';
|
||||
|
|
@ -99,8 +94,9 @@ class _ReportScreenState extends State<ReportScreen> {
|
|||
final Map<DateTime, Map<String, Duration>> dailyTagDurations = {};
|
||||
|
||||
for (final entry in entries) {
|
||||
if (entry.durationSecs == null || entry.durationSecs!.toInt() <= 0)
|
||||
if (entry.durationSecs == null || entry.durationSecs!.toInt() <= 0) {
|
||||
continue;
|
||||
}
|
||||
|
||||
final entryDay = DateTime(
|
||||
entry.startDateTime.year,
|
||||
|
|
@ -393,14 +389,24 @@ class _ReportScreenState extends State<ReportScreen> {
|
|||
children: [
|
||||
_buildFilterControls(tagOptions),
|
||||
Expanded(
|
||||
child:
|
||||
_isLoadingReport
|
||||
? Center(child: PlatformCircularProgressIndicator())
|
||||
: _reportData == null
|
||||
? const Center(
|
||||
child: Text('Keine Reportdaten gefunden oder Fehler.'),
|
||||
)
|
||||
: _buildReportView(),
|
||||
child: RefreshIndicator.adaptive(
|
||||
onRefresh: _generateReport,
|
||||
child:
|
||||
_isLoadingReport
|
||||
? Center(child: PlatformCircularProgressIndicator())
|
||||
: _reportData == null
|
||||
? ListView(
|
||||
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(
|
||||
height: 250,
|
||||
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) {
|
||||
showPlatformDialog(
|
||||
context: context,
|
||||
|
|
@ -855,10 +762,7 @@ class _ReportScreenState extends State<ReportScreen> {
|
|||
actions: <Widget>[
|
||||
PlatformDialogAction(
|
||||
child: PlatformText('OK'),
|
||||
onPressed:
|
||||
() => Navigator.pop(
|
||||
dialogContext,
|
||||
), // Pop using dialog's context
|
||||
onPressed: () => Navigator.pop(dialogContext),
|
||||
),
|
||||
],
|
||||
),
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue