feat: add pickable daterange to report screen
This commit is contained in:
parent
9fe11e4796
commit
169669315c
4 changed files with 258 additions and 35 deletions
|
|
@ -3,6 +3,7 @@ import 'dart:developer';
|
||||||
import 'dart:io';
|
import 'dart:io';
|
||||||
|
|
||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
|
import 'package:flutter_localizations/flutter_localizations.dart';
|
||||||
import 'package:path_provider/path_provider.dart';
|
import 'package:path_provider/path_provider.dart';
|
||||||
import 'package:timetracker/screens/main_screen.dart';
|
import 'package:timetracker/screens/main_screen.dart';
|
||||||
import 'package:timetracker/src/rust/api.dart';
|
import 'package:timetracker/src/rust/api.dart';
|
||||||
|
|
@ -68,6 +69,12 @@ class MyApp extends StatelessWidget {
|
||||||
Widget build(BuildContext context) {
|
Widget build(BuildContext context) {
|
||||||
return MaterialApp(
|
return MaterialApp(
|
||||||
title: 'Flutter Rust Time Tracker',
|
title: 'Flutter Rust Time Tracker',
|
||||||
|
supportedLocales: const [Locale('de', 'DE'), Locale('en', '')],
|
||||||
|
localizationsDelegates: const [
|
||||||
|
GlobalMaterialLocalizations.delegate,
|
||||||
|
GlobalWidgetsLocalizations.delegate,
|
||||||
|
GlobalCupertinoLocalizations.delegate,
|
||||||
|
],
|
||||||
theme: ThemeData(primarySwatch: Colors.blue, useMaterial3: true),
|
theme: ThemeData(primarySwatch: Colors.blue, useMaterial3: true),
|
||||||
home: const InitializerWidget(),
|
home: const InitializerWidget(),
|
||||||
);
|
);
|
||||||
|
|
|
||||||
|
|
@ -1,9 +1,11 @@
|
||||||
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';
|
||||||
|
import 'package:font_awesome_flutter/font_awesome_flutter.dart';
|
||||||
import 'package:intl/intl.dart';
|
import 'package:intl/intl.dart';
|
||||||
import 'package:provider/provider.dart';
|
import 'package:provider/provider.dart';
|
||||||
import 'package:timetracker/src/rust/api.dart';
|
import 'package:timetracker/src/rust/api.dart';
|
||||||
|
|
@ -56,7 +58,7 @@ extension ReportDataFormatting on ReportData {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
enum ReportPeriod { day, week, month, year }
|
enum ReportPeriod { day, week, month, year, custom }
|
||||||
|
|
||||||
class ReportScreen extends StatefulWidget {
|
class ReportScreen extends StatefulWidget {
|
||||||
const ReportScreen({super.key});
|
const ReportScreen({super.key});
|
||||||
|
|
@ -68,6 +70,8 @@ class ReportScreen extends StatefulWidget {
|
||||||
class _ReportScreenState extends State<ReportScreen> {
|
class _ReportScreenState extends State<ReportScreen> {
|
||||||
final DateTime _selectedDate = DateTime.now();
|
final DateTime _selectedDate = DateTime.now();
|
||||||
ReportPeriod _selectedPeriod = ReportPeriod.day;
|
ReportPeriod _selectedPeriod = ReportPeriod.day;
|
||||||
|
DateTime _customStartDate = DateTime.now();
|
||||||
|
DateTime _customEndDate = DateTime.now();
|
||||||
Tag? _selectedTag;
|
Tag? _selectedTag;
|
||||||
ReportData? _reportData;
|
ReportData? _reportData;
|
||||||
bool _isLoadingReport = false;
|
bool _isLoadingReport = false;
|
||||||
|
|
@ -85,6 +89,9 @@ class _ReportScreenState extends State<ReportScreen> {
|
||||||
@override
|
@override
|
||||||
void initState() {
|
void initState() {
|
||||||
super.initState();
|
super.initState();
|
||||||
|
final now = DateTime.now();
|
||||||
|
_customStartDate = DateTime(now.year, now.month, now.day);
|
||||||
|
_customEndDate = DateTime(now.year, now.month, now.day);
|
||||||
_generateReport();
|
_generateReport();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -330,7 +337,7 @@ class _ReportScreenState extends State<ReportScreen> {
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
(DateTime, DateTime) _calculateDateRange() {
|
(DateTime, DateTime) _calculateDateRange(ReportPeriod period) {
|
||||||
final nowLocal = DateTime.now().toLocal();
|
final nowLocal = DateTime.now().toLocal();
|
||||||
final startOfDay = DateTime(nowLocal.year, nowLocal.month, nowLocal.day);
|
final startOfDay = DateTime(nowLocal.year, nowLocal.month, nowLocal.day);
|
||||||
|
|
||||||
|
|
@ -352,6 +359,13 @@ class _ReportScreenState extends State<ReportScreen> {
|
||||||
final startOfYear = DateTime(nowLocal.year, 1, 1);
|
final startOfYear = DateTime(nowLocal.year, 1, 1);
|
||||||
final endOfYear = DateTime(nowLocal.year + 1, 1, 1);
|
final endOfYear = DateTime(nowLocal.year + 1, 1, 1);
|
||||||
return (startOfYear, endOfYear);
|
return (startOfYear, endOfYear);
|
||||||
|
case ReportPeriod.custom:
|
||||||
|
final endExclusive = DateTime(
|
||||||
|
_customEndDate.year,
|
||||||
|
_customEndDate.month,
|
||||||
|
_customEndDate.day,
|
||||||
|
).add(const Duration(days: 1));
|
||||||
|
return (_customStartDate, endExclusive);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -363,12 +377,33 @@ class _ReportScreenState extends State<ReportScreen> {
|
||||||
});
|
});
|
||||||
|
|
||||||
final timeService = context.read<TimeTrackingService>();
|
final timeService = context.read<TimeTrackingService>();
|
||||||
final (start, end) = _calculateDateRange();
|
|
||||||
final tagId = _selectedTag?.id;
|
final tagId = _selectedTag?.id;
|
||||||
|
|
||||||
log("Generating report for TagID: $tagId, Start: $start, End: $end");
|
late DateTime reportStartDate;
|
||||||
|
late DateTime reportEndDateExclusive;
|
||||||
|
|
||||||
final data = await timeService.flGetReport(tagId, start, end);
|
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) {
|
if (mounted) {
|
||||||
setState(() {
|
setState(() {
|
||||||
|
|
@ -378,6 +413,29 @@ class _ReportScreenState extends State<ReportScreen> {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// 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
|
@override
|
||||||
Widget build(BuildContext context) {
|
Widget build(BuildContext context) {
|
||||||
final tags = context.watch<TimeTrackingService>().tags;
|
final tags = context.watch<TimeTrackingService>().tags;
|
||||||
|
|
@ -405,7 +463,7 @@ class _ReportScreenState extends State<ReportScreen> {
|
||||||
),
|
),
|
||||||
],
|
],
|
||||||
)
|
)
|
||||||
: _buildReportView(),
|
: _buildReportView(_reportData!),
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
],
|
],
|
||||||
|
|
@ -415,33 +473,71 @@ class _ReportScreenState extends State<ReportScreen> {
|
||||||
|
|
||||||
Widget _buildFilterControls(List<Tag?> tagOptions) {
|
Widget _buildFilterControls(List<Tag?> tagOptions) {
|
||||||
final DateFormat formatter = DateFormat('dd.MM.yyyy');
|
final DateFormat formatter = DateFormat('dd.MM.yyyy');
|
||||||
final (start, end) = _calculateDateRange();
|
final String dateRangeButtonText =
|
||||||
|
'${formatter.format(_customStartDate)} - ${formatter.format(_customEndDate)}';
|
||||||
|
|
||||||
return Padding(
|
return Padding(
|
||||||
padding: const EdgeInsets.all(8.0),
|
padding: const EdgeInsets.all(8.0),
|
||||||
child: Wrap(
|
child: Wrap(
|
||||||
spacing: 8.0,
|
spacing: 8.0,
|
||||||
runSpacing: 4.0,
|
runSpacing: 4.0,
|
||||||
|
alignment: WrapAlignment.start,
|
||||||
|
crossAxisAlignment: WrapCrossAlignment.center,
|
||||||
children: [
|
children: [
|
||||||
DropdownButton<ReportPeriod>(
|
PlatformWidget(
|
||||||
|
material:
|
||||||
|
(_, __) => DropdownButton<ReportPeriod>(
|
||||||
value: _selectedPeriod,
|
value: _selectedPeriod,
|
||||||
onChanged: (ReportPeriod? newValue) {
|
items: [
|
||||||
if (newValue != null) {
|
...ReportPeriod.values.map((period) {
|
||||||
setState(() {
|
String text;
|
||||||
_selectedPeriod = newValue;
|
switch (period) {
|
||||||
});
|
case ReportPeriod.day:
|
||||||
_generateReport();
|
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;
|
||||||
}
|
}
|
||||||
},
|
|
||||||
items:
|
|
||||||
ReportPeriod.values.map((ReportPeriod period) {
|
|
||||||
return DropdownMenuItem<ReportPeriod>(
|
return DropdownMenuItem<ReportPeriod>(
|
||||||
value: period,
|
value: period,
|
||||||
child: PlatformText(
|
child: PlatformText(text),
|
||||||
period.toString().split('.').last.toUpperCase(),
|
|
||||||
),
|
|
||||||
);
|
);
|
||||||
}).toList(),
|
}),
|
||||||
|
],
|
||||||
|
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?>(
|
DropdownButton<Tag?>(
|
||||||
|
|
@ -462,23 +558,135 @@ class _ReportScreenState extends State<ReportScreen> {
|
||||||
}).toList(),
|
}).toList(),
|
||||||
),
|
),
|
||||||
|
|
||||||
Chip(
|
if (_selectedPeriod == ReportPeriod.custom)
|
||||||
label: PlatformText(
|
PlatformElevatedButton(
|
||||||
'${formatter.format(start)} - ${formatter.format(end.subtract(const Duration(seconds: 1)))}',
|
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 _buildReportView() {
|
// 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) {
|
if (_reportData == null) {
|
||||||
return Center(
|
return Center(
|
||||||
child: PlatformText('Keine Reportdaten geladen oder Fehler.'),
|
child: PlatformText('Keine Reportdaten geladen oder Fehler.'),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
final (startDate, endDate) = _calculateDateRange();
|
final (startDate, endDate) = _calculateDateRange(
|
||||||
|
_selectedPeriod == ReportPeriod.custom
|
||||||
|
? ReportPeriod.custom
|
||||||
|
: _selectedPeriod,
|
||||||
|
);
|
||||||
|
|
||||||
return ListView(
|
return ListView(
|
||||||
children: [
|
children: [
|
||||||
|
|
|
||||||
|
|
@ -123,6 +123,11 @@ packages:
|
||||||
url: "https://pub.dev"
|
url: "https://pub.dev"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "5.0.0"
|
version: "5.0.0"
|
||||||
|
flutter_localizations:
|
||||||
|
dependency: "direct main"
|
||||||
|
description: flutter
|
||||||
|
source: sdk
|
||||||
|
version: "0.0.0"
|
||||||
flutter_platform_widgets:
|
flutter_platform_widgets:
|
||||||
dependency: "direct main"
|
dependency: "direct main"
|
||||||
description:
|
description:
|
||||||
|
|
@ -174,10 +179,10 @@ packages:
|
||||||
dependency: "direct main"
|
dependency: "direct main"
|
||||||
description:
|
description:
|
||||||
name: intl
|
name: intl
|
||||||
sha256: "3df61194eb431efc39c4ceba583b95633a403f46c9fd341e550ce0bfa50e9aa5"
|
sha256: d6f56758b7d3014a48af9701c085700aac781a92a87a62b1333b46d8879661cf
|
||||||
url: "https://pub.dev"
|
url: "https://pub.dev"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "0.20.2"
|
version: "0.19.0"
|
||||||
leak_tracker:
|
leak_tracker:
|
||||||
dependency: transitive
|
dependency: transitive
|
||||||
description:
|
description:
|
||||||
|
|
|
||||||
|
|
@ -10,6 +10,8 @@ environment:
|
||||||
dependencies:
|
dependencies:
|
||||||
flutter:
|
flutter:
|
||||||
sdk: flutter
|
sdk: flutter
|
||||||
|
flutter_localizations:
|
||||||
|
sdk: flutter
|
||||||
|
|
||||||
cupertino_icons: ^1.0.8
|
cupertino_icons: ^1.0.8
|
||||||
rust_lib_timetracker:
|
rust_lib_timetracker:
|
||||||
|
|
@ -17,7 +19,8 @@ dependencies:
|
||||||
flutter_rust_bridge: 2.9.0
|
flutter_rust_bridge: 2.9.0
|
||||||
ffi: ^2.1.0
|
ffi: ^2.1.0
|
||||||
path_provider: ^2.1.1
|
path_provider: ^2.1.1
|
||||||
intl: ^0.20.2
|
# intl: ^0.20.2
|
||||||
|
intl: ^0.19.0
|
||||||
provider: ^6.1.1
|
provider: ^6.1.1
|
||||||
fl_chart: ^0.70.2
|
fl_chart: ^0.70.2
|
||||||
flutter_platform_widgets: ^8.0.0
|
flutter_platform_widgets: ^8.0.0
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue