From f679ea672b5e9c6e2603c6a88fa521238d9ec037 Mon Sep 17 00:00:00 2001 From: Herwig Birke Date: Fri, 10 Oct 2025 09:53:16 +0200 Subject: [PATCH] Monthy calculation --- lib/api/booking_api.dart | 1 + lib/screens/monthly_view.dart | 320 ++++++++++++++---- macos/Flutter/GeneratedPluginRegistrant.swift | 4 + pubspec.lock | 159 ++++++++- pubspec.yaml | 5 +- 5 files changed, 412 insertions(+), 77 deletions(-) diff --git a/lib/api/booking_api.dart b/lib/api/booking_api.dart index d666803..be13913 100644 --- a/lib/api/booking_api.dart +++ b/lib/api/booking_api.dart @@ -1,3 +1,4 @@ +import 'dart:typed_data'; import 'dart:convert'; import 'package:http/http.dart' as http; import '../models/work_day.dart'; diff --git a/lib/screens/monthly_view.dart b/lib/screens/monthly_view.dart index b9d983f..a4b22b6 100644 --- a/lib/screens/monthly_view.dart +++ b/lib/screens/monthly_view.dart @@ -1,4 +1,5 @@ -import 'dart:async'; // << neu: für Timer (Debounce) +import 'dart:async'; +import 'dart:convert'; // <-- für jsonEncode (Debug) import 'dart:ui' show FontFeature; import 'package:flutter/material.dart'; import 'package:http/http.dart' as http; @@ -27,6 +28,9 @@ class _MonthlyViewState extends State { static const double _wNumber = 76; static const double _fontSize = 12; + // API Base (nur für Debug-Ausgabe) + static const String _apiUrl = 'https://api.windesign.at/workinghours.php'; + // Codes, die Zeiten leeren/sperren static const Set _lockCodes = {'G', 'K', 'U', 'SU'}; @@ -34,7 +38,8 @@ class _MonthlyViewState extends State { final Set _invalidCells = {}; final Map _focusNodes = {}; final Map _controllers = {}; - FocusNode _nodeFor(String key) => _focusNodes.putIfAbsent(key, () => FocusNode()); + FocusNode _nodeFor(String key) => + _focusNodes.putIfAbsent(key, () => FocusNode()); TextEditingController _controllerFor(String key, String initial) => _controllers.putIfAbsent(key, () => TextEditingController(text: initial)); @@ -68,11 +73,12 @@ class _MonthlyViewState extends State { late final ScrollController _vCtrl; bool _syncingH = false; - // --- NEU: Debounce + Save-Status pro Zeile --- - final Map _saveTimers = {}; // rowIndex -> Timer - final Set _savingRows = {}; // Zeilen, die gerade speichern - final Set _justSavedRows = {}; // Zeilen, die eben gespeichert haben (Häkchen kurz anzeigen) - final Map _rowSaveError = {}; // Zeilenfehler + // Save-Status pro Zeile + final Map _saveTimers = {}; // rowIndex -> Timer + final Set _savingRows = {}; // Zeilen, die gerade speichern + final Set _justSavedRows = + {}; // Zeilen, die eben gespeichert haben (Häkchen kurz anzeigen) + final Map _rowSaveError = {}; // Zeilenfehler @override void initState() { @@ -118,6 +124,9 @@ class _MonthlyViewState extends State { } Future _loadMonth(DateTime m) async { + // DEBUG + print('[monthly] LOAD month=${m.year}-${m.month.toString().padLeft(2, '0')}'); + setState(() { _loading = true; _error = null; @@ -125,14 +134,14 @@ class _MonthlyViewState extends State { }); try { final results = await Future.wait([ - _bookingApi.getBookingList(m), // List - _dailyApi.getDailyMinutes(), // Map - _bookingApi.getMonthStart(m), // MonthStart + _bookingApi.getBookingList(m), // List + _dailyApi.getDailyMinutes(), // Map + _bookingApi.getMonthStart(m), // MonthStart ]); final apiDays = results[0] as List; - final plan = results[1] as Map; - final mStart = results[2] as MonthStart; + final plan = results[1] as Map; + final mStart = results[2] as MonthStart; final holidayMap = buildHolidayMapAT(m.year); @@ -140,10 +149,12 @@ class _MonthlyViewState extends State { final withTargets = filled.map((d) { final isHoliday = holidayMap.containsKey(ymd(d.date)); // Feiertage haben immer Soll 0 - final baseTarget = isHoliday ? 0 : (plan[d.date.weekday] ?? d.targetMinutes); + final baseTarget = + isHoliday ? 0 : (plan[d.date.weekday] ?? d.targetMinutes); // U/SU/K ebenfalls Soll 0 final code = d.code; - final target = (code == 'U' || code == 'SU' || code == 'K') ? 0 : baseTarget; + final target = + (code == 'U' || code == 'SU' || code == 'K') ? 0 : baseTarget; return WorkDay( date: d.date, intervals: d.intervals, @@ -158,7 +169,8 @@ class _MonthlyViewState extends State { _days = withTargets; _dailyPlan = plan; _monthStartInfo = mStart; - _carryBaseMinutes = mStart.carryBaseMinutes; // starthours + overtime + correction + _carryBaseMinutes = + mStart.carryBaseMinutes; // starthours + overtime + correction _loading = false; // Status-Maps leeren (neuer Monat) @@ -167,8 +179,16 @@ class _MonthlyViewState extends State { _rowSaveError.clear(); }); + // DEBUG: geladene Monats-Startdaten + print('[monthly] monthStart loaded for ${ymd(_monthStart)} ' + 'carryBase=${_carryBaseMinutes} ' + 'startvacation=${_monthStartInfo?.startVacationUnits ?? 0} ' + 'overtime=${_monthStartInfo?.overtimeMinutes ?? 0} ' + 'correction=${_monthStartInfo?.correctionMinutes ?? 0}'); + _syncControllersWithDays(); } catch (e) { + print('[monthly] LOAD ERROR: $e'); setState(() { _error = e.toString(); _loading = false; @@ -193,7 +213,8 @@ class _MonthlyViewState extends State { } } - Color? _rowColorFor(WorkDay d, {required Color? holidayBg, required Color? weekendBg}) { + Color? _rowColorFor(WorkDay d, + {required Color? holidayBg, required Color? weekendBg}) { switch (d.code) { case 'G': return const Color(0xFFBFBFFF); // Gleitzeit @@ -202,12 +223,13 @@ class _MonthlyViewState extends State { case 'SU': return const Color(0xFF7F7FFF); // Sonderurlaub case 'K': - return Colors.yellow; // Krankenstand + return Colors.yellow; // Krankenstand case 'T': - return Colors.red; // Training + return Colors.red; // Training } final isHoliday = _holidays.containsKey(ymd(d.date)); - final isWeekend = d.date.weekday == DateTime.saturday || d.date.weekday == DateTime.sunday; + final isWeekend = d.date.weekday == DateTime.saturday || + d.date.weekday == DateTime.sunday; if (isHoliday) return holidayBg; if (isWeekend) return weekendBg; return null; @@ -215,7 +237,6 @@ class _MonthlyViewState extends State { @override Widget build(BuildContext context) { - final headerColumns = _buildColumns(); final bodyColumns = _buildColumns(); final theme = Theme.of(context); @@ -223,7 +244,8 @@ class _MonthlyViewState extends State { final weekendBg = Colors.grey.withOpacity(0.30); // Live-„Effective“-Tage (inkl. Eingabetexte + Sperrlogik) - final effectiveDays = List.generate(_days.length, (i) => _effectiveDay(i, _days[i])); + final effectiveDays = + List.generate(_days.length, (i) => _effectiveDay(i, _days[i])); // Tagesdifferenzen & kumuliert (Start mit Monatssaldo aus API) final diffs = []; @@ -257,7 +279,8 @@ class _MonthlyViewState extends State { }; final correctionMin = _monthStartInfo?.correctionMinutes ?? 0; - final nextCarryMin = cumulative.isNotEmpty ? cumulative.last : _carryBaseMinutes; + final nextCarryMin = + cumulative.isNotEmpty ? cumulative.last : _carryBaseMinutes; final rows = List.generate(_days.length, (i) { final day = effectiveDays[i]; @@ -274,8 +297,10 @@ class _MonthlyViewState extends State { _MonthHeader( month: _monthStart, loading: _loading, - onPrev: () => _loadMonth(DateTime(_monthStart.year, _monthStart.month - 1, 1)), - onNext: () => _loadMonth(DateTime(_monthStart.year, _monthStart.month + 1, 1)), + onPrev: () => + _loadMonth(DateTime(_monthStart.year, _monthStart.month - 1, 1)), + onNext: () => + _loadMonth(DateTime(_monthStart.year, _monthStart.month + 1, 1)), onPickMonth: () async { final picked = await showDatePicker( context: context, @@ -300,8 +325,10 @@ class _MonthlyViewState extends State { crossAxisAlignment: CrossAxisAlignment.start, mainAxisSize: MainAxisSize.min, children: [ - Text('Fehler beim Laden:', style: TextStyle(fontSize: _fontSize)), - SelectableText(_error ?? '', style: const TextStyle(fontSize: _fontSize)), + Text('Fehler beim Laden:', + style: TextStyle(fontSize: _fontSize)), + SelectableText(_error ?? '', + style: const TextStyle(fontSize: _fontSize)), ], ), actions: [ @@ -324,7 +351,8 @@ class _MonthlyViewState extends State { data: DataTableThemeData( headingRowHeight: 30, columnSpacing: 10, - headingTextStyle: const TextStyle(fontWeight: FontWeight.w700, fontSize: _fontSize), + headingTextStyle: const TextStyle( + fontWeight: FontWeight.w700, fontSize: _fontSize), ), child: const _HeaderOnlyDataTable(), ), @@ -397,7 +425,9 @@ class _MonthlyViewState extends State { } List _buildColumns() { - DataColumn c(String label, {Alignment align = Alignment.center, double? width}) => DataColumn( + DataColumn c(String label, + {Alignment align = Alignment.center, double? width}) => + DataColumn( label: SizedBox( width: width, child: Align( @@ -433,14 +463,16 @@ class _MonthlyViewState extends State { ]; } - List _buildEditableCells(int dayIndex, WorkDay day, int runningDiff) { - final leftLabel = rightDayLabel(day.date); // "Mo 01.09." - final rightLabel = leftDayLabel(day.date); // "01.09. Mo" + List _buildEditableCells( + int dayIndex, WorkDay day, int runningDiff) { + final leftLabel = rightDayLabel(day.date); // "Mo 01.09." + final rightLabel = leftDayLabel(day.date); // "01.09. Mo" final hName = _holidays[ymd(day.date)] ?? ''; final bool lockTimes = day.code != null && _lockCodes.contains(day.code); final bool isHoliday = _holidays.containsKey(ymd(day.date)); - final bool isWeekend = day.date.weekday == DateTime.saturday || day.date.weekday == DateTime.sunday; + final bool isWeekend = day.date.weekday == DateTime.saturday || + day.date.weekday == DateTime.sunday; final bool codeDisabled = isHoliday || isWeekend; String slotText(int slot, bool isStart) { @@ -480,7 +512,7 @@ class _MonthlyViewState extends State { ), ), const SizedBox(width: 4), - _rowStatusBadge(dayIndex), // << neu: Spinner/Häkchen/Fault + _rowStatusBadge(dayIndex), // Spinner/Häkchen/Fehler ], ), ), @@ -603,7 +635,8 @@ class _MonthlyViewState extends State { ))); cells.add(DataCell(SizedBox( width: _wNumber, - child: Align(alignment: Alignment.centerRight, child: _monoSmall(diffSum)), + child: + Align(alignment: Alignment.centerRight, child: _monoSmall(diffSum)), ))); // 17: Datum (linksbündig) @@ -615,7 +648,8 @@ class _MonthlyViewState extends State { ), ))); - assert(cells.length == 18, 'Row has ${cells.length} cells but expected 18.'); + assert( + cells.length == 18, 'Row has ${cells.length} cells but expected 18.'); return cells; } @@ -628,7 +662,7 @@ class _MonthlyViewState extends State { ), ); - // --- NEU: kleiner Status-Badge pro Zeile --- + // kleiner Status-Badge pro Zeile Widget _rowStatusBadge(int row) { if (_savingRows.contains(row)) { return const SizedBox( @@ -638,7 +672,8 @@ class _MonthlyViewState extends State { ); } if (_rowSaveError.containsKey(row)) { - return Icon(Icons.error_outline, size: 14, color: Theme.of(context).colorScheme.error); + return Icon(Icons.error_outline, + size: 14, color: Theme.of(context).colorScheme.error); } if (_justSavedRows.contains(row)) { return const Icon(Icons.check_circle, size: 14, color: Colors.green); @@ -657,20 +692,30 @@ class _MonthlyViewState extends State { final label = v == null ? '—' : codeLabel(v); // langer Name im Dropdown return DropdownMenuItem( value: v, - child: Text(label, textAlign: TextAlign.center, style: const TextStyle(fontSize: _fontSize)), + child: Text(label, + textAlign: TextAlign.center, + style: const TextStyle(fontSize: _fontSize)), ); }).toList(), selectedItemBuilder: (context) { return values.map((v) { final shortText = v ?? '—'; // kurzer Name in der Tabelle - return Center(child: Text(shortText, style: const TextStyle(fontSize: _fontSize))); + return Center( + child: + Text(shortText, style: const TextStyle(fontSize: _fontSize))); }).toList(); }, onChanged: disabled ? null : (newCode) { final d = _days[dayIndex]; - final bool willLock = newCode != null && _lockCodes.contains(newCode); + final bool willLock = + newCode != null && _lockCodes.contains(newCode); + + // DEBUG + print('[monthly] CODE change day=${ymd(d.date)} newCode=${newCode ?? '-'} ' + '(lockTimes=$willLock)'); + setState(() { final newDays = List.from(_days); newDays[dayIndex] = WorkDay( @@ -693,7 +738,7 @@ class _MonthlyViewState extends State { } }); - _scheduleSave(dayIndex); // << neu: debounce statt sofort speichern + _scheduleSave(dayIndex); }, ); } @@ -715,6 +760,11 @@ class _MonthlyViewState extends State { if (d.code != null && _lockCodes.contains(d.code)) return; + // DEBUG + print('[monthly] TIME commit day=${ymd(d.date)} ' + 'slot=${slot + 1} ${isStart ? 'start' : 'end'}="$text" ' + 'parsed=${t != null ? fmtTimeOfDay(t) : '-'}'); + final starts = List.filled(5, null); final ends = List.filled(5, null); @@ -747,21 +797,20 @@ class _MonthlyViewState extends State { _days = newDays; }); - _scheduleSave(dayIndex); // << neu: debounce + _scheduleSave(dayIndex); } - // --- NEU: Debounce + Save-Status-Handling --- + // Debounce für Tages-Save void _scheduleSave(int dayIndex) { - // alten Timer stoppen _saveTimers[dayIndex]?.cancel(); - // neuen Timer setzen _saveTimers[dayIndex] = Timer(const Duration(milliseconds: 500), () async { + // DEBUG + print('[monthly] SAVE (debounced) row=$dayIndex date=${ymd(_days[dayIndex].date)}'); await _saveDay(dayIndex); }); } Future _saveDay(int dayIndex) async { - // evtl. laufenden Debounce-Timer löschen (wir speichern jetzt) _saveTimers[dayIndex]?.cancel(); _saveTimers.remove(dayIndex); @@ -773,7 +822,19 @@ class _MonthlyViewState extends State { try { final effective = _effectiveDay(dayIndex, _days[dayIndex]); - await _bookingApi.saveDay(effective); + + // --- DEBUG: booking/saveDay Aufruf + Payload spiegeln --- + final payload = _debugDayPayload(effective); + print('[api] POST $_apiUrl'); + print('[api] body=${jsonEncode(payload)}'); + + await _bookingApi.saveDay(effective); // tatsächlicher API-Call + + // DEBUG + print('[api] booking/saveDay OK date=${ymd(effective.date)}'); + + // Folgemonat mitrechnen/speichern + await _saveMonthStartNow(); if (!mounted) return; setState(() { @@ -789,12 +850,12 @@ class _MonthlyViewState extends State { }); }); } catch (e) { + print('[api] booking/saveDay ERROR: $e'); if (!mounted) return; setState(() { _savingRows.remove(dayIndex); _rowSaveError[dayIndex] = e.toString(); }); - // optional non-intrusive Snack ScaffoldMessenger.of(context).showSnackBar( SnackBar( content: Text('Speichern fehlgeschlagen: $e'), @@ -805,6 +866,59 @@ class _MonthlyViewState extends State { } } + Future _saveMonthStartNow() async { + // 1) Folgemonat bestimmen + final nextMonth = DateTime(_monthStart.year, _monthStart.month + 1, 1); + + // 2) Effektive Tage (inkl. Eingabefelder berücksichtigen) + final effectiveDays = + List.generate(_days.length, (i) => _effectiveDay(i, _days[i])); + + // 3) Saldo des Monats berechnen (Start + Summe(IST - SOLL)) + int saldoMin = _carryBaseMinutes; + for (final d in effectiveDays) { + final worked = _workedFor(d); + final target = d.targetMinutes; + saldoMin += worked - target; + } + + // 4) Urlaubs-Übertrag berechnen + final startVacation = _monthStartInfo?.startVacationUnits ?? 0; + final usedVacation = _days.where((d) => d.code == 'U').length; + final carryVacation = startVacation - usedVacation; + + // 5) Overtime/Correction aus Startdaten übernehmen + final overtime = _monthStartInfo?.overtimeMinutes ?? 0; + final correction = _monthStartInfo?.correctionMinutes ?? 0; + + // --- DEBUG: monthlybooking/saveStart Aufruf + Payload spiegeln --- + final payload = { + 'module': 'monthlybooking', + 'function': 'saveStart', + 'date': + '${nextMonth.year.toString().padLeft(4, '0')}-${nextMonth.month.toString().padLeft(2, '0')}-01', + 'starthours': saldoMin, + 'startvacation': carryVacation, + 'overtime': overtime, + 'correction': correction, + }; + print('[api] POST $_apiUrl'); + print('[api] body=${jsonEncode(payload)}'); + + try { + await _bookingApi.saveMonthStart( + nextMonth, + starthours: saldoMin, + startvacation: carryVacation, + overtime: overtime, + correction: correction, + ); + print('[api] monthlybooking/saveStart OK for ${ymd(nextMonth)}'); + } catch (e) { + print('[api] monthlybooking/saveStart ERROR: $e'); + } + } + void _syncControllersWithDays() { for (int i = 0; i < _days.length; i++) { final lock = _days[i].code != null && _lockCodes.contains(_days[i].code); @@ -823,8 +937,12 @@ class _MonthlyViewState extends State { : ''; final cs = _controllers[sKey]; final ce = _controllers[eKey]; - if (cs != null && cs.text != sText && !(_focusNodes[sKey]?.hasFocus ?? false)) cs.text = sText; - if (ce != null && ce.text != eText && !(_focusNodes[eKey]?.hasFocus ?? false)) ce.text = eText; + if (cs != null && + cs.text != sText && + !(_focusNodes[sKey]?.hasFocus ?? false)) cs.text = sText; + if (ce != null && + ce.text != eText && + !(_focusNodes[eKey]?.hasFocus ?? false)) ce.text = eText; } } } @@ -866,8 +984,12 @@ class _MonthlyViewState extends State { final sText = _controllers[sKey]?.text; final eText = _controllers[eKey]?.text; - final s = (sText != null && sText.isNotEmpty) ? parseTextHHMM(sText) : baseSlot(base, slot, true); - final e = (eText != null && eText.isNotEmpty) ? parseTextHHMM(eText) : baseSlot(base, slot, false); + final s = (sText != null && sText.isNotEmpty) + ? parseTextHHMM(sText) + : baseSlot(base, slot, true); + final e = (eText != null && eText.isNotEmpty) + ? parseTextHHMM(eText) + : baseSlot(base, slot, false); if (s != null && e != null) intervals.add(WorkInterval(s, e)); } @@ -929,6 +1051,40 @@ class _MonthlyViewState extends State { if (a == Alignment.centerLeft) return TextAlign.left; return TextAlign.center; } + + // ------------------------ DEBUG-Helper ------------------------ + + Map _debugDayPayload(WorkDay day) { + // Intervalle als 5 Slots wie im echten POST + final starts = List.filled(5, null); + final ends = List.filled(5, null); + for (int i = 0; i < day.intervals.length && i < 5; i++) { + starts[i] = fmtTimeOfDay(day.intervals[i].start); + ends[i] = fmtTimeOfDay(day.intervals[i].end); + } + final lock = _lockCodes.contains(day.code); + + final y = day.date.year.toString().padLeft(4, '0'); + final m = day.date.month.toString().padLeft(2, '0'); + final d = day.date.day.toString().padLeft(2, '0'); + + return { + 'module': 'booking', + 'function': 'saveDay', + 'date': '$y-$m-$d', + 'code': day.code, // null → wird als NULL gespeichert + 'come1': lock ? null : starts[0], + 'leave1': lock ? null : ends[0], + 'come2': lock ? null : starts[1], + 'leave2': lock ? null : ends[1], + 'come3': lock ? null : starts[2], + 'leave3': lock ? null : ends[2], + 'come4': lock ? null : starts[3], + 'leave4': lock ? null : ends[3], + 'come5': lock ? null : starts[4], + 'leave5': lock ? null : ends[4], + }; + } } /// Kopfzeile als separate DataTable (fixiert) @@ -969,16 +1125,26 @@ class _MonthHeader extends StatelessWidget { return Padding( padding: const EdgeInsets.symmetric(horizontal: 4.0), child: Row(children: [ - IconButton(onPressed: loading ? null : onPrev, icon: const Icon(Icons.chevron_left)), + IconButton( + onPressed: loading ? null : onPrev, + icon: const Icon(Icons.chevron_left)), Expanded( child: TextButton.icon( onPressed: loading ? null : onPickMonth, icon: const Icon(Icons.calendar_month), - label: Text(title, style: Theme.of(context).textTheme.titleLarge?.copyWith(fontSize: 16)), + label: Text(title, + style: Theme.of(context) + .textTheme + .titleLarge + ?.copyWith(fontSize: 16)), ), ), - IconButton(onPressed: loading ? null : onReload, icon: const Icon(Icons.refresh)), - IconButton(onPressed: loading ? null : onNext, icon: const Icon(Icons.chevron_right)), + IconButton( + onPressed: loading ? null : onReload, + icon: const Icon(Icons.refresh)), + IconButton( + onPressed: loading ? null : onNext, + icon: const Icon(Icons.chevron_right)), ]), ); } @@ -991,14 +1157,14 @@ class _MonthlySummaryFooter extends StatelessWidget { // Linke Spalte (Strings bereits formatiert) final String uebertragStartText; // Startsaldo (Minuten → HH:MM) - final String sollText; // Summe Soll (HH:MM) - final String istText; // Summe Ist (HH:MM) - final String correctionText; // Correction (HH:MM, ±) - final String saldoText; // IST - SOLL (±HH:MM) - final String paidOvertimeText; // folgt später (—) - final String uebertragNextText; // letzter „Differenz gesamt“ (±HH:MM) - final String restUrlaubText; // startvacation (Zahl) - final String urlaubUebertragText;// startvacation - used + final String sollText; // Summe Soll (HH:MM) + final String istText; // Summe Ist (HH:MM) + final String correctionText; // Correction (HH:MM, ±) + final String saldoText; // IST - SOLL (±HH:MM) + final String paidOvertimeText; // folgt später (—) + final String uebertragNextText; // letzter „Differenz gesamt“ (±HH:MM) + final String restUrlaubText; // startvacation (Zahl) + final String urlaubUebertragText; // startvacation - used // Rechte Spalte (Counts) final int countGleitzeit; @@ -1036,16 +1202,17 @@ class _MonthlySummaryFooter extends StatelessWidget { // Monatslabels: Vormonat für Überträge/Resturlaub, aktueller Monat für Soll/Ist final prev = DateTime(month.year, month.month - 1, 1); final prevLabel = monthTitle(prev); - final curLabel = monthLabel; + final curLabel = monthLabel; // Layout-Konstanten - const double leftValueWidth = 120; // Werte-Breite links (rechtsbündig) - const double rightValueWidth = 80; // Werte-Breite rechts (rechtsbündig) - const double colGap = 24; // Abstand zwischen Spalten - const double rowGap = 2; // Zeilenabstand - const double footerMaxWidth = 720; // maximale Footer-Breite - - final labelStyle = TextStyle(fontSize: fontSize, color: Theme.of(context).colorScheme.onSurface); + const double leftValueWidth = 120; // Werte-Breite links (rechtsbündig) + const double rightValueWidth = 80; // Werte-Breite rechts (rechtsbündig) + const double colGap = 24; // Abstand zwischen Spalten + const double rowGap = 2; // Zeilenabstand + const double footerMaxWidth = 720; // maximale Footer-Breite + + final labelStyle = TextStyle( + fontSize: fontSize, color: Theme.of(context).colorScheme.onSurface); final valueStyle = TextStyle( fontSize: fontSize, fontFeatures: const [FontFeature.tabularFigures()], @@ -1097,7 +1264,8 @@ class _MonthlySummaryFooter extends StatelessWidget { width: leftValueWidth, child: Align( alignment: Alignment.centerRight, - child: Text(value, style: valueStyle, textAlign: TextAlign.right), + child: + Text(value, style: valueStyle, textAlign: TextAlign.right), ), ), ], @@ -1116,7 +1284,8 @@ class _MonthlySummaryFooter extends StatelessWidget { width: rightValueWidth, child: Align( alignment: Alignment.centerRight, - child: Text(value, style: valueStyle, textAlign: TextAlign.right), + child: + Text(value, style: valueStyle, textAlign: TextAlign.right), ), ), const SizedBox(width: 12), @@ -1158,7 +1327,8 @@ class _MonthlySummaryFooter extends StatelessWidget { Expanded( child: Column( mainAxisSize: MainAxisSize.min, - children: rightItems.map((e) => rightRow(e.$1, e.$2)).toList(), + children: + rightItems.map((e) => rightRow(e.$1, e.$2)).toList(), ), ), ], diff --git a/macos/Flutter/GeneratedPluginRegistrant.swift b/macos/Flutter/GeneratedPluginRegistrant.swift index cccf817..d484543 100644 --- a/macos/Flutter/GeneratedPluginRegistrant.swift +++ b/macos/Flutter/GeneratedPluginRegistrant.swift @@ -5,6 +5,10 @@ import FlutterMacOS import Foundation +import file_picker +import path_provider_foundation func RegisterGeneratedPlugins(registry: FlutterPluginRegistry) { + FilePickerPlugin.register(with: registry.registrar(forPlugin: "FilePickerPlugin")) + PathProviderPlugin.register(with: registry.registrar(forPlugin: "PathProviderPlugin")) } diff --git a/pubspec.lock b/pubspec.lock index f734aa1..aecb96a 100644 --- a/pubspec.lock +++ b/pubspec.lock @@ -1,6 +1,14 @@ # Generated by pub # See https://dart.dev/tools/pub/glossary#lockfile packages: + args: + dependency: transitive + description: + name: args + sha256: d0481093c50b1da8910eb0bb301626d4d8eb7284aa739614d2b394ee09e3ea04 + url: "https://pub.dev" + source: hosted + version: "2.7.0" async: dependency: transitive description: @@ -41,6 +49,14 @@ packages: url: "https://pub.dev" source: hosted version: "1.18.0" + cross_file: + dependency: transitive + description: + name: cross_file + sha256: "7caf6a750a0c04effbb52a676dce9a4a592e10ad35c34d6d2d0e4811160d5670" + url: "https://pub.dev" + source: hosted + version: "0.3.4+2" cupertino_icons: dependency: "direct main" description: @@ -49,6 +65,14 @@ packages: url: "https://pub.dev" source: hosted version: "1.0.8" + dbus: + dependency: transitive + description: + name: dbus + sha256: "79e0c23480ff85dc68de79e2cd6334add97e48f7f4865d17686dd6ea81a47e8c" + url: "https://pub.dev" + source: hosted + version: "0.7.11" fake_async: dependency: transitive description: @@ -57,6 +81,22 @@ packages: url: "https://pub.dev" source: hosted version: "1.3.1" + ffi: + dependency: transitive + description: + name: ffi + sha256: "16ed7b077ef01ad6170a3d0c57caa4a112a38d7a2ed5602e0aca9ca6f3d98da6" + url: "https://pub.dev" + source: hosted + version: "2.1.3" + file_picker: + dependency: "direct main" + description: + name: file_picker + sha256: f2d9f173c2c14635cc0e9b14c143c49ef30b4934e8d1d274d6206fcb0086a06f + url: "https://pub.dev" + source: hosted + version: "10.3.3" flutter: dependency: "direct main" description: flutter @@ -70,11 +110,24 @@ packages: url: "https://pub.dev" source: hosted version: "5.0.0" + flutter_plugin_android_lifecycle: + dependency: transitive + description: + name: flutter_plugin_android_lifecycle + sha256: "1c2b787f99bdca1f3718543f81d38aa1b124817dfeb9fb196201bea85b6134bf" + url: "https://pub.dev" + source: hosted + version: "2.0.26" flutter_test: dependency: "direct dev" description: flutter source: sdk version: "0.0.0" + flutter_web_plugins: + dependency: transitive + description: flutter + source: sdk + version: "0.0.0" http: dependency: "direct main" description: @@ -147,6 +200,14 @@ packages: url: "https://pub.dev" source: hosted version: "1.15.0" + open_filex: + dependency: "direct main" + description: + name: open_filex + sha256: "9976da61b6a72302cf3b1efbce259200cd40232643a467aac7370addf94d6900" + url: "https://pub.dev" + source: hosted + version: "4.7.0" path: dependency: transitive description: @@ -155,6 +216,78 @@ packages: url: "https://pub.dev" source: hosted version: "1.9.0" + path_provider: + dependency: "direct main" + description: + name: path_provider + sha256: "50c5dd5b6e1aaf6fb3a78b33f6aa3afca52bf903a8a5298f53101fdaee55bbcd" + url: "https://pub.dev" + source: hosted + version: "2.1.5" + path_provider_android: + dependency: transitive + description: + name: path_provider_android + sha256: "4adf4fd5423ec60a29506c76581bc05854c55e3a0b72d35bb28d661c9686edf2" + url: "https://pub.dev" + source: hosted + version: "2.2.15" + path_provider_foundation: + dependency: transitive + description: + name: path_provider_foundation + sha256: "4843174df4d288f5e29185bd6e72a6fbdf5a4a4602717eed565497429f179942" + url: "https://pub.dev" + source: hosted + version: "2.4.1" + path_provider_linux: + dependency: transitive + description: + name: path_provider_linux + sha256: f7a1fe3a634fe7734c8d3f2766ad746ae2a2884abe22e241a8b301bf5cac3279 + url: "https://pub.dev" + source: hosted + version: "2.2.1" + path_provider_platform_interface: + dependency: transitive + description: + name: path_provider_platform_interface + sha256: "88f5779f72ba699763fa3a3b06aa4bf6de76c8e5de842cf6f29e2e06476c2334" + url: "https://pub.dev" + source: hosted + version: "2.1.2" + path_provider_windows: + dependency: transitive + description: + name: path_provider_windows + sha256: bd6f00dbd873bfb70d0761682da2b3a2c2fccc2b9e84c495821639601d81afe7 + url: "https://pub.dev" + source: hosted + version: "2.3.0" + petitparser: + dependency: transitive + description: + name: petitparser + sha256: c15605cd28af66339f8eb6fbe0e541bfe2d1b72d5825efc6598f3e0a31b9ad27 + url: "https://pub.dev" + source: hosted + version: "6.0.2" + platform: + dependency: transitive + description: + name: platform + sha256: "5d6b1b0036a5f331ebc77c850ebc8506cbc1e9416c27e59b439f917a902a4984" + url: "https://pub.dev" + source: hosted + version: "3.1.6" + plugin_platform_interface: + dependency: transitive + description: + name: plugin_platform_interface + sha256: "4820fbfdb9478b1ebae27888254d445073732dae3d6ea81f0b7e06d5dedc3f02" + url: "https://pub.dev" + source: hosted + version: "2.1.8" sky_engine: dependency: transitive description: flutter @@ -240,6 +373,30 @@ packages: url: "https://pub.dev" source: hosted version: "1.1.1" + win32: + dependency: transitive + description: + name: win32 + sha256: daf97c9d80197ed7b619040e86c8ab9a9dad285e7671ee7390f9180cc828a51e + url: "https://pub.dev" + source: hosted + version: "5.10.1" + xdg_directories: + dependency: transitive + description: + name: xdg_directories + sha256: "7a3f37b05d989967cdddcbb571f1ea834867ae2faa29725fd085180e0883aa15" + url: "https://pub.dev" + source: hosted + version: "1.1.0" + xml: + dependency: transitive + description: + name: xml + sha256: b015a8ad1c488f66851d762d3090a21c600e479dc75e68328c52774040cf9226 + url: "https://pub.dev" + source: hosted + version: "6.5.0" sdks: dart: ">=3.5.4 <4.0.0" - flutter: ">=3.18.0-18.0.pre.54" + flutter: ">=3.24.0" diff --git a/pubspec.yaml b/pubspec.yaml index fdef1b3..aab1787 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -35,7 +35,10 @@ dependencies: # The following adds the Cupertino Icons font to your application. # Use with the CupertinoIcons class for iOS style icons. cupertino_icons: ^1.0.8 - http: ^1.5.0 + http: any + path_provider: any + open_filex: any + file_picker: any dev_dependencies: flutter_test: