timetracker/lib/screens/report_screen.dart

979 lines
31 KiB
Dart

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';
import 'package:font_awesome_flutter/font_awesome_flutter.dart';
import 'package:intl/intl.dart';
import 'package:provider/provider.dart';
import 'package:timetracker/src/rust/api.dart';
import 'package:timetracker/time_tracking_service.dart';
int dateTimeToUnixSeconds(DateTime dt) =>
dt.toUtc().millisecondsSinceEpoch ~/ 1000;
DateTime unixSecondsToDateTime(int ts) =>
DateTime.fromMillisecondsSinceEpoch(ts * 1000, isUtc: true).toLocal();
extension TimeEntryFormatting on TimeEntry {
DateTime get startDateTime =>
DateTime.fromMillisecondsSinceEpoch(
startTime.toInt() * 1000,
isUtc: true,
).toLocal();
DateTime? get endDateTime =>
endTime == null
? null
: DateTime.fromMillisecondsSinceEpoch(
endTime!.toInt() * 1000,
isUtc: true,
).toLocal();
String get durationFormatted {
final durationInSeconds = durationSecs?.toInt();
if (durationInSeconds == null || durationInSeconds < 0) return '--:--:--';
final duration = Duration(seconds: durationInSeconds);
String twoDigits(int n) => n.toString().padLeft(2, "0");
final hours = twoDigits(duration.inHours);
final minutes = twoDigits(duration.inMinutes.remainder(60));
final seconds = twoDigits(duration.inSeconds.remainder(60));
return "$hours:$minutes:$seconds";
}
}
extension ReportDataFormatting on ReportData {
String get totalDurationFormatted {
final durationInSeconds = totalDurationSecs.toInt();
if (durationInSeconds < 0) return '--:--:--';
final duration = Duration(seconds: durationInSeconds);
String twoDigits(int n) => n.toString().padLeft(2, "0");
final hours = twoDigits(duration.inHours);
final minutes = twoDigits(duration.inMinutes.remainder(60));
final seconds = twoDigits(duration.inSeconds.remainder(60));
return "$hours:$minutes:$seconds";
}
}
enum ReportPeriod { day, week, month, year, custom }
class ReportScreen extends StatefulWidget {
const ReportScreen({super.key});
@override
State<ReportScreen> createState() => _ReportScreenState();
}
class _ReportScreenState extends State<ReportScreen> {
final DateTime _selectedDate = DateTime.now();
ReportPeriod _selectedPeriod = ReportPeriod.day;
DateTime _customStartDate = DateTime.now();
DateTime _customEndDate = DateTime.now();
Tag? _selectedTag;
ReportData? _reportData;
bool _isLoadingReport = false;
TimeEntry? _editingEntry;
String _formatDuration(Duration duration) {
String twoDigits(int n) => n.toString().padLeft(2, "0");
if (duration.isNegative) return "--:--:--";
final hours = twoDigits(duration.inHours);
final minutes = twoDigits(duration.inMinutes.remainder(60));
final seconds = twoDigits(duration.inSeconds.remainder(60));
return "$hours:$minutes:$seconds";
}
@override
void initState() {
super.initState();
final now = DateTime.now();
_customStartDate = DateTime(now.year, now.month, now.day);
_customEndDate = DateTime(now.year, now.month, now.day);
_generateReport();
}
Map<DateTime, Map<String, Duration>> _aggregateDataForBarChart(
List<TimeEntry> entries,
) {
final Map<DateTime, Map<String, Duration>> dailyTagDurations = {};
for (final entry in entries) {
if (entry.durationSecs == null || entry.durationSecs!.toInt() <= 0) {
continue;
}
final entryDay = DateTime(
entry.startDateTime.year,
entry.startDateTime.month,
entry.startDateTime.day,
);
final tagName = entry.tagName ?? 'Ohne Tag';
final duration = Duration(seconds: entry.durationSecs!.toInt());
final dailyMap = dailyTagDurations.putIfAbsent(entryDay, () => {});
final currentDuration = dailyMap.putIfAbsent(
tagName,
() => Duration.zero,
);
dailyMap[tagName] = currentDuration + duration;
}
return dailyTagDurations;
}
Widget _buildBarChart(
ReportData reportData,
DateTime startDate,
DateTime endDate,
) {
final aggregatedData = _aggregateDataForBarChart(reportData.entries);
if (aggregatedData.isEmpty) {
return Center(
child: PlatformText("Keine Daten für Balkendiagramm vorhanden."),
);
}
final uniqueTags = <String>{};
for (var dailyMap in aggregatedData.values) {
uniqueTags.addAll(dailyMap.keys);
}
final sortedTags = uniqueTags.toList()..sort();
final List<Color> predefinedColors = [
Colors.blue,
Colors.red,
Colors.green,
Colors.orange,
Colors.purple,
Colors.teal,
Colors.pink,
Colors.amber,
Colors.indigo,
Colors.cyan,
];
final tagColors = <String, Color>{};
for (int i = 0; i < sortedTags.length; i++) {
tagColors[sortedTags[i]] = predefinedColors[i % predefinedColors.length];
}
final sortedDays = aggregatedData.keys.toList()..sort();
double maxY = 0;
for (var day in sortedDays) {
double dailyTotalHours = 0;
for (var duration in aggregatedData[day]!.values) {
dailyTotalHours += duration.inMinutes / 60.0;
}
if (dailyTotalHours > maxY) {
maxY = dailyTotalHours;
}
}
maxY = (maxY * 1.1).ceilToDouble();
if (maxY < 1) maxY = 1;
final List<BarChartGroupData> barGroups = [];
final double barWidth = 12;
final double spaceBetweenBars = 2;
final double groupWidth = (barWidth + spaceBetweenBars) * sortedTags.length;
for (int dayIndex = 0; dayIndex < sortedDays.length; dayIndex++) {
final day = sortedDays[dayIndex];
final dailyMap = aggregatedData[day]!;
final List<BarChartRodData> barRods = [];
for (int tagIndex = 0; tagIndex < sortedTags.length; tagIndex++) {
final tagName = sortedTags[tagIndex];
final duration = dailyMap[tagName] ?? Duration.zero;
final hours = duration.inMinutes / 60.0;
barRods.add(
BarChartRodData(
toY: hours,
color: tagColors[tagName],
width: barWidth,
borderRadius: BorderRadius.zero,
),
);
}
barGroups.add(
BarChartGroupData(
x: dayIndex,
barsSpace: spaceBetweenBars,
barRods: barRods,
),
);
}
Widget bottomTitleWidgets(double value, TitleMeta meta) {
final index = value.toInt();
final showEvery = (sortedDays.length / 7).ceil();
if (index < 0 || index >= sortedDays.length || index % showEvery != 0) {
return Container();
}
final day = sortedDays[index];
String text;
if (_selectedPeriod == ReportPeriod.day ||
_selectedPeriod == ReportPeriod.week) {
text = DateFormat('E', 'de_DE').format(day);
} else {
text = DateFormat('dd.MM').format(day);
}
return SideTitleWidget(
space: 4,
meta: TitleMeta(
min: 0.0,
max: 10.0,
parentAxisSize: 10,
axisPosition: 0.0,
appliedInterval: 10,
sideTitles: SideTitles(),
formattedValue: '',
axisSide: AxisSide.left,
rotationQuarterTurns: 0,
),
child: Text(text, style: const TextStyle(fontSize: 10)),
);
}
Widget leftTitleWidgets(double value, TitleMeta meta) {
if (value == 0 || value == meta.max) {
return Container();
}
if (value % 1 == 0) {
return SideTitleWidget(
space: 4,
meta: TitleMeta(
min: 0.0,
max: 10.0,
parentAxisSize: 10,
axisPosition: 0.0,
appliedInterval: 10,
sideTitles: SideTitles(),
formattedValue: '',
axisSide: AxisSide.left,
rotationQuarterTurns: 0,
),
child: Text(
'${value.toInt()}h',
style: const TextStyle(fontSize: 10),
),
);
}
return Container();
}
return Padding(
padding: const EdgeInsets.all(16.0),
child: BarChart(
BarChartData(
maxY: maxY,
barGroups: barGroups,
groupsSpace: groupWidth + 10,
titlesData: FlTitlesData(
show: true,
rightTitles: const AxisTitles(
sideTitles: SideTitles(showTitles: false),
),
topTitles: const AxisTitles(
sideTitles: SideTitles(showTitles: false),
),
bottomTitles: AxisTitles(
sideTitles: SideTitles(
showTitles: true,
getTitlesWidget: bottomTitleWidgets,
reservedSize: 20,
),
),
leftTitles: AxisTitles(
sideTitles: SideTitles(
showTitles: true,
reservedSize: 30,
getTitlesWidget: leftTitleWidgets,
),
),
),
gridData: FlGridData(
show: true,
drawVerticalLine: false,
getDrawingHorizontalLine: (value) {
return FlLine(
color: Colors.grey.withOpacity(0.3),
strokeWidth: 1,
);
},
),
borderData: FlBorderData(show: false),
barTouchData: BarTouchData(
touchTooltipData: BarTouchTooltipData(
getTooltipItem: (group, groupIndex, rod, rodIndex) {
final tagName = sortedTags[rodIndex];
final duration = Duration(minutes: (rod.toY * 60).round());
return BarTooltipItem(
'$tagName\n',
const TextStyle(
color: Colors.white,
fontWeight: FontWeight.bold,
),
children: <TextSpan>[
TextSpan(
text: _formatDuration(duration),
style: const TextStyle(
color: Colors.white,
fontWeight: FontWeight.normal,
),
),
],
);
},
),
),
),
),
);
}
(DateTime, DateTime) _calculateDateRange(ReportPeriod period) {
final nowLocal = DateTime.now().toLocal();
final startOfDay = DateTime(nowLocal.year, nowLocal.month, nowLocal.day);
switch (_selectedPeriod) {
case ReportPeriod.day:
final endOfDay = startOfDay.add(const Duration(days: 1));
return (startOfDay, endOfDay);
case ReportPeriod.week:
final startOfWeek = startOfDay.subtract(
Duration(days: startOfDay.weekday - 1),
);
final endOfWeek = startOfWeek.add(const Duration(days: 7));
return (startOfWeek, endOfWeek);
case ReportPeriod.month:
final startOfMonth = DateTime(nowLocal.year, nowLocal.month, 1);
final endOfMonth = DateTime(nowLocal.year, nowLocal.month + 1, 1);
return (startOfMonth, endOfMonth);
case ReportPeriod.year:
final startOfYear = DateTime(nowLocal.year, 1, 1);
final endOfYear = DateTime(nowLocal.year + 1, 1, 1);
return (startOfYear, endOfYear);
case ReportPeriod.custom:
final endExclusive = DateTime(
_customEndDate.year,
_customEndDate.month,
_customEndDate.day,
).add(const Duration(days: 1));
return (_customStartDate, endExclusive);
}
}
Future<void> _generateReport() async {
if (!mounted) return;
setState(() {
_isLoadingReport = true;
_reportData = null;
});
final timeService = context.read<TimeTrackingService>();
final tagId = _selectedTag?.id;
late DateTime reportStartDate;
late DateTime reportEndDateExclusive;
if (_selectedPeriod == ReportPeriod.custom) {
reportStartDate = _customStartDate;
reportEndDateExclusive = DateTime(
_customEndDate.year,
_customEndDate.month,
_customEndDate.day,
).add(const Duration(days: 1));
} else {
final (start, end) = _calculateDateRange(_selectedPeriod);
reportStartDate = start;
reportEndDateExclusive = end;
}
log(
"Generating report for Period: $_selectedPeriod, TagID: $tagId, Start: $reportStartDate, End (Exclusive): $reportEndDateExclusive",
);
final data = await timeService.flGetReport(
tagId,
reportStartDate,
reportEndDateExclusive,
);
if (mounted) {
setState(() {
_reportData = data;
_isLoadingReport = false;
});
}
}
// Future<void> _generateReport() async {
// if (!mounted) return;
// setState(() {
// _isLoadingReport = true;
// _reportData = null;
// });
// final timeService = context.read<TimeTrackingService>();
// final (start, end) = _calculateDateRange();
// final tagId = _selectedTag?.id;
// log("Generating report for TagID: $tagId, Start: $start, End: $end");
// final data = await timeService.flGetReport(tagId, start, end);
// if (mounted) {
// setState(() {
// _reportData = data;
// _isLoadingReport = false;
// });
// }
// }
@override
Widget build(BuildContext context) {
final tags = context.watch<TimeTrackingService>().tags;
final List<Tag?> tagOptions = [null, ...tags];
return PlatformScaffold(
appBar: PlatformAppBar(title: PlatformText('Reports')),
body: Column(
children: [
_buildFilterControls(tagOptions),
Expanded(
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(_reportData!),
),
),
],
),
);
}
Widget _buildFilterControls(List<Tag?> tagOptions) {
final DateFormat formatter = DateFormat('dd.MM.yyyy');
final String dateRangeButtonText =
'${formatter.format(_customStartDate)} - ${formatter.format(_customEndDate)}';
return Padding(
padding: const EdgeInsets.all(8.0),
child: Wrap(
spacing: 8.0,
runSpacing: 4.0,
alignment: WrapAlignment.start,
crossAxisAlignment: WrapCrossAlignment.center,
children: [
PlatformWidget(
material:
(_, __) => DropdownButton<ReportPeriod>(
value: _selectedPeriod,
items: [
...ReportPeriod.values.map((period) {
String text;
switch (period) {
case ReportPeriod.day:
text = 'Tag';
break;
case ReportPeriod.week:
text = 'Woche';
break;
case ReportPeriod.month:
text = 'Monat';
break;
case ReportPeriod.year:
text = 'Jahr';
break;
case ReportPeriod.custom:
text = 'Benutzerdefiniert';
break;
}
return DropdownMenuItem<ReportPeriod>(
value: period,
child: PlatformText(text),
);
}),
],
onChanged: (ReportPeriod? newValue) {
if (newValue != null && newValue != _selectedPeriod) {
setState(() {
_selectedPeriod = newValue;
if (_selectedPeriod != ReportPeriod.custom) {
_generateReport();
} else {
// Optional: Direkt den Picker öffnen, wenn "Custom" gewählt wird?
// WidgetsBinding.instance.addPostFrameCallback((_) => _selectDateRange());
}
});
}
},
),
// TODO: Cupertino Dropdown Alternative (komplexer, oft wird Button+Picker genutzt)
// Fürs Erste verwenden wir auch auf iOS das Material Dropdown
cupertino:
(_, __) => CupertinoButton(
padding: EdgeInsets.zero,
child: Text(_selectedPeriod.toString().split('.').last),
onPressed: () {
/* Hier müsste ein Picker geöffnet werden */
},
),
),
DropdownButton<Tag?>(
value: _selectedTag,
hint: PlatformText("Alle Tags"),
onChanged: (Tag? newValue) {
setState(() {
_selectedTag = newValue;
});
_generateReport();
},
items:
tagOptions.map((Tag? tag) {
return DropdownMenuItem<Tag?>(
value: tag,
child: PlatformText(tag?.name ?? "Alle Tags"),
);
}).toList(),
),
if (_selectedPeriod == ReportPeriod.custom)
PlatformElevatedButton(
onPressed: _selectDateRange,
child: Row(
mainAxisSize: MainAxisSize.min,
children: [
Icon(FontAwesomeIcons.calendar), // PlatformIcons(context)),
const SizedBox(width: 8),
PlatformText(dateRangeButtonText),
],
),
padding: const EdgeInsets.symmetric(horizontal: 12, vertical: 8),
),
],
),
);
}
// Widget _buildFilterControls(List<Tag?> tagOptions) {
// final DateFormat formatter = DateFormat('dd.MM.yyyy');
// final (start, end) = _calculateDateRange();
// return Padding(
// padding: const EdgeInsets.all(8.0),
// child: Wrap(
// spacing: 8.0,
// runSpacing: 4.0,
// alignment: WrapAlignment.center,
// children: [
// DropdownButton<ReportPeriod>(
// value: _selectedPeriod,
// onChanged: (ReportPeriod? newValue) {
// if (newValue != null) {
// setState(() {
// _selectedPeriod = newValue;
// });
// _generateReport();
// }
// },
// items:
// ReportPeriod.values.map((ReportPeriod period) {
// return DropdownMenuItem<ReportPeriod>(
// value: period,
// child: PlatformText(
// period.toString().split('.').last.toUpperCase(),
// ),
// );
// }).toList(),
// ),
// DropdownButton<Tag?>(
// value: _selectedTag,
// hint: PlatformText("Alle Tags"),
// onChanged: (Tag? newValue) {
// setState(() {
// _selectedTag = newValue;
// });
// _generateReport();
// },
// items:
// tagOptions.map((Tag? tag) {
// return DropdownMenuItem<Tag?>(
// value: tag,
// child: PlatformText(tag?.name ?? "Alle Tags"),
// );
// }).toList(),
// ),
// Chip(
// label: PlatformText(
// '${formatter.format(start)} - ${formatter.format(end.subtract(const Duration(seconds: 1)))}',
// ),
// ),
// ],
// ),
// );
// }
Future<void> _selectDateRange() async {
final DateTimeRange initialRange = DateTimeRange(
start: _customStartDate,
end: _customEndDate,
);
final DateTimeRange? picked = await showDateRangePicker(
context: context,
locale: const Locale('de', 'DE'),
initialDateRange: initialRange,
firstDate: DateTime(2020),
lastDate: DateTime.now().add(const Duration(days: 365)),
helpText: 'Zeitraum auswählen',
cancelText: 'Abbrechen',
confirmText: 'Ok',
saveText: 'Speichern',
);
if (picked != null) {
if (picked.start != _customStartDate ||
picked.end != _customEndDate ||
_selectedPeriod != ReportPeriod.custom) {
setState(() {
_customStartDate = DateTime(
picked.start.year,
picked.start.month,
picked.start.day,
);
_customEndDate = DateTime(
picked.end.year,
picked.end.month,
picked.end.day,
);
_selectedPeriod = ReportPeriod.custom;
});
_generateReport();
}
}
}
Widget _buildReportView(ReportData reportData) {
if (_reportData == null) {
return Center(
child: PlatformText('Keine Reportdaten geladen oder Fehler.'),
);
}
final (startDate, endDate) = _calculateDateRange(
_selectedPeriod == ReportPeriod.custom
? ReportPeriod.custom
: _selectedPeriod,
);
return ListView(
children: [
Padding(
padding: const EdgeInsets.all(16.0),
child: PlatformText(
'Gesamtdauer: ${_reportData!.totalDurationFormatted}',
style: platformThemeData(
context,
material: (data) => data.textTheme.titleLarge,
cupertino: (data) => data.textTheme.navTitleTextStyle,
),
),
),
SizedBox(
height: 250,
child: _buildBarChart(_reportData!, startDate, endDate),
),
const Divider(),
Padding(
padding: const EdgeInsets.symmetric(vertical: 8.0, horizontal: 16.0),
child: PlatformText(
"Einträge (zum Löschen wischen)",
style: platformThemeData(
context,
material: (data) => data.textTheme.titleMedium,
cupertino:
(data) =>
data.textTheme.navTitleTextStyle.copyWith(fontSize: 18),
),
),
),
SizedBox(height: 600, child: _buildReportList(_reportData!.entries)),
],
);
}
void _confirmAndDeleteEntry(TimeEntry entry, BuildContext context) {
showPlatformDialog(
context: context,
builder:
(_) => PlatformAlertDialog(
title: Text('Eintrag löschen?'),
actions: [
PlatformDialogAction(
child: PlatformText('Abbrechen'),
onPressed: () => Navigator.pop(context),
),
PlatformDialogAction(
child: PlatformText('Löschen'),
onPressed: () async {
Navigator.pop(context);
await _deleteEntry(entry);
},
),
],
),
);
}
Future<void> _deleteEntry(TimeEntry entry) async {
final service = Provider.of<TimeTrackingService>(context, listen: false);
try {
await service.flDeleteTimeEntry(entry.id);
if (mounted) {
setState(() => _reportData!.entries.remove(entry));
}
await _generateReport();
} catch (e) {
ScaffoldMessenger.of(
context,
).showSnackBar(SnackBar(content: Text('Fehler beim Löschen: $e')));
}
}
Widget _buildReportList(List<TimeEntry> entries) {
return ListView.builder(
// shrinkWrap: true,
// physics: const NeverScrollableScrollPhysics(),
itemCount: entries.length,
itemBuilder: (context, index) {
final entry = entries[index];
return Builder(
builder:
(slidableContext) => Slidable(
endActionPane: ActionPane(
motion: const ScrollMotion(),
children: [
SlidableAction(
onPressed:
(_) => _confirmAndDeleteEntry(entry, slidableContext),
backgroundColor: Colors.red,
icon: Icons.delete,
label: 'Löschen',
),
SlidableAction(
onPressed:
(context) => _showEditEntryDialog(context, entry),
backgroundColor: Colors.blue,
icon: Icons.edit,
label: 'Bearbeiten',
),
],
),
child: ListTile(
title: Text(entry.tagName ?? 'Ohne Tag'),
subtitle: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Text(
'Start: ${DateFormat('HH:mm').format(entry.startDateTime)}',
),
if (entry.endDateTime != null)
Text(
'Ende: ${DateFormat('HH:mm').format(entry.endDateTime!)}',
),
Text('Dauer: ${entry.durationFormatted}'),
],
),
trailing: const Icon(Icons.swipe_left),
),
),
);
},
);
}
Future<void> _showEditEntryDialog(
BuildContext context,
TimeEntry entry,
) async {
DateTime? startTime = entry.startDateTime;
DateTime? endTime = entry.endDateTime;
await showPlatformDialog(
context: context,
builder: (BuildContext context) {
return PlatformAlertDialog(
title: Text('Eintrag bearbeiten'),
content: StatefulBuilder(
builder: (BuildContext context, StateSetter setState) {
return Column(
mainAxisSize: MainAxisSize.min,
children: <Widget>[
PlatformText('Startzeit:'),
PlatformElevatedButton(
child: Text(
DateFormat('yyyy-MM-dd HH:mm').format(startTime!),
),
onPressed: () async {
DateTime? pickedDate = await showPlatformDatePicker(
context: context,
initialDate: startTime!,
firstDate: DateTime(2000),
lastDate: DateTime(2101),
);
if (pickedDate != null) {
TimeOfDay? pickedTime = await showTimePicker(
context: context,
initialTime: TimeOfDay.fromDateTime(startTime!),
);
if (pickedTime != null) {
setState(() {
startTime = DateTime(
pickedDate.year,
pickedDate.month,
pickedDate.day,
pickedTime.hour,
pickedTime.minute,
);
});
}
}
},
),
PlatformText('Endzeit:'),
PlatformElevatedButton(
child: Text(
endTime != null
? DateFormat('yyyy-MM-dd HH:mm').format(endTime!)
: 'Keine Endzeit',
),
onPressed: () async {
DateTime? pickedDate = await showPlatformDatePicker(
context: context,
initialDate: endTime ?? DateTime.now(),
firstDate: DateTime(2000),
lastDate: DateTime(2101),
);
if (pickedDate != null) {
TimeOfDay? pickedTime = await showTimePicker(
context: context,
initialTime: TimeOfDay.fromDateTime(
endTime ?? DateTime.now(),
),
);
if (pickedTime != null) {
setState(() {
endTime = DateTime(
pickedDate.year,
pickedDate.month,
pickedDate.day,
pickedTime.hour,
pickedTime.minute,
);
});
}
}
},
),
],
);
},
),
actions: [
PlatformDialogAction(
child: PlatformText('Abbrechen'),
onPressed: () {
Navigator.of(context).pop();
},
),
PlatformDialogAction(
child: PlatformText('Speichern'),
onPressed: () async {
// Hier die Logik zum Speichern der bearbeiteten Daten
Navigator.of(context).pop();
_updateTimeEntry(entry, startTime, endTime);
},
),
],
);
},
);
}
Future<void> _updateTimeEntry(
TimeEntry entry,
DateTime? startTime,
DateTime? endTime,
) async {
final timeService = Provider.of<TimeTrackingService>(
context,
listen: false,
);
if (startTime == null || endTime == null) {
log("Start- oder Endzeit darf nicht null sein.");
return;
}
bool success = await timeService.flUpdateTimeEntry(
entryId: entry.id,
tagId: entry.tagId,
startTime: startTime,
endTime: endTime,
);
if (success) {
_generateReport();
} else {
_showPlatformFeedbackDialog(
context,
'Error',
'Fehler beim Aktualisieren des TimeEntry.',
);
}
}
void _showPlatformFeedbackDialog(
BuildContext context,
String title,
String message,
) {
showPlatformDialog(
context: context,
useRootNavigator: true,
builder:
(dialogContext) => PlatformAlertDialog(
title: PlatformText(title),
content: PlatformText(message),
actions: <Widget>[
PlatformDialogAction(
child: PlatformText('OK'),
onPressed: () => Navigator.pop(dialogContext),
),
],
),
);
}
}