Skip to content

Commit dd4ea49

Browse files
sprobst76claude
andcommitted
feat(debug): enhance Geofence Debug Screen with Force Sync
- Add "Events jetzt verarbeiten" (Force Sync) button - Show WorkEntry status (running/stopped) with duration - Display last sync result count - Use real zone IDs for test ENTER/EXIT events - Add helpful hints about why events might not be processed - Show warning when no work entry is running This helps diagnose why ENTER events might not create work entries. Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
1 parent 39b4827 commit dd4ea49

File tree

3 files changed

+169
-5
lines changed

3 files changed

+169
-5
lines changed

CHANGELOG.md

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,19 @@ und dieses Projekt folgt [Semantic Versioning](https://semver.org/lang/de/).
1414

1515
---
1616

17+
## [0.1.0-beta.26] - 2026-01-12
18+
19+
### Verbessert
20+
- **Geofence Debug Screen**: Erweiterte Diagnose-Funktionen
21+
- Neuer "Events jetzt verarbeiten" Button (Force Sync)
22+
- Arbeitszeit-Status Anzeige (läuft/gestoppt)
23+
- Laufzeit-Anzeige bei aktiver Arbeitszeit
24+
- Letzter Sync-Ergebnis Anzeige
25+
- Test-Events nutzen jetzt echte Zone-IDs
26+
- Bessere Hinweise warum Events nicht verarbeitet werden
27+
28+
---
29+
1730
## [0.1.0-beta.25] - 2026-01-12
1831

1932
### Hinzugefügt

lib/screens/geofence_debug_screen.dart

Lines changed: 155 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,10 @@ import 'package:share_plus/share_plus.dart';
88
import '../models/geofence_zone.dart';
99
import '../providers.dart';
1010
import '../services/geofence_event_queue.dart';
11+
import '../services/geofence_sync_service.dart';
12+
import '../models/work_entry.dart';
1113
import '../theme/theme_colors.dart';
14+
import 'package:hive/hive.dart';
1215

1316
class GeofenceDebugScreen extends ConsumerStatefulWidget {
1417
const GeofenceDebugScreen({super.key});
@@ -42,6 +45,10 @@ class _GeofenceDebugScreenState extends ConsumerState<GeofenceDebugScreen> {
4245
// Logs
4346
final List<_LogEntry> _logs = [];
4447

48+
// Work Entry Status
49+
WorkEntry? _runningEntry;
50+
int? _lastSyncResult;
51+
4552
@override
4653
void initState() {
4754
super.initState();
@@ -61,10 +68,45 @@ class _GeofenceDebugScreenState extends ConsumerState<GeofenceDebugScreen> {
6168
_loadPermissions(),
6269
_loadPosition(),
6370
_loadEventQueue(),
71+
_loadWorkEntryStatus(),
6472
]);
6573
setState(() => _lastRefresh = DateTime.now());
6674
}
6775

76+
Future<void> _loadWorkEntryStatus() async {
77+
try {
78+
final workBox = Hive.box<WorkEntry>('work');
79+
final running = workBox.values.where((e) => e.stop == null).toList();
80+
if (mounted) {
81+
setState(() {
82+
_runningEntry = running.isNotEmpty ? running.last : null;
83+
});
84+
}
85+
} catch (e) {
86+
_addLog('Fehler beim Laden des WorkEntry-Status: $e', isError: true);
87+
}
88+
}
89+
90+
Future<void> _forceSyncNow() async {
91+
_addLog('Force Sync gestartet...');
92+
try {
93+
final workBox = Hive.box<WorkEntry>('work');
94+
final syncService = GeofenceSyncService(workBox);
95+
final processedCount = await syncService.syncPendingEvents();
96+
97+
setState(() => _lastSyncResult = processedCount);
98+
_addLog('Sync abgeschlossen: $processedCount Events verarbeitet');
99+
100+
if (processedCount > 0) {
101+
ref.invalidate(workListProvider);
102+
}
103+
104+
await _loadAll();
105+
} catch (e) {
106+
_addLog('Sync Fehler: $e', isError: true);
107+
}
108+
}
109+
68110
Future<void> _loadPermissions() async {
69111
final results = await Future.wait([
70112
Permission.location.status,
@@ -194,6 +236,10 @@ class _GeofenceDebugScreenState extends ConsumerState<GeofenceDebugScreen> {
194236
_buildZonesCard(zones),
195237
const SizedBox(height: 16),
196238

239+
// Work Entry Status Section
240+
_buildWorkEntryCard(),
241+
const SizedBox(height: 16),
242+
197243
// Event Queue Section
198244
_buildEventQueueCard(),
199245
const SizedBox(height: 16),
@@ -602,6 +648,88 @@ class _GeofenceDebugScreenState extends ConsumerState<GeofenceDebugScreen> {
602648
);
603649
}
604650

651+
Widget _buildWorkEntryCard() {
652+
return Card(
653+
color: _runningEntry != null ? context.successBackground : null,
654+
child: Padding(
655+
padding: const EdgeInsets.all(16),
656+
child: Column(
657+
crossAxisAlignment: CrossAxisAlignment.start,
658+
children: [
659+
Row(
660+
children: [
661+
Icon(
662+
_runningEntry != null ? Icons.play_circle : Icons.stop_circle,
663+
color: _runningEntry != null ? context.successForeground : Colors.grey,
664+
),
665+
const SizedBox(width: 8),
666+
const Text(
667+
'Arbeitszeit-Status',
668+
style: TextStyle(fontSize: 16, fontWeight: FontWeight.bold),
669+
),
670+
const Spacer(),
671+
Container(
672+
padding: const EdgeInsets.symmetric(horizontal: 8, vertical: 2),
673+
decoration: BoxDecoration(
674+
color: _runningEntry != null
675+
? Colors.green.withAlpha(51)
676+
: Colors.grey.withAlpha(51),
677+
borderRadius: BorderRadius.circular(8),
678+
),
679+
child: Text(
680+
_runningEntry != null ? 'Läuft' : 'Gestoppt',
681+
style: TextStyle(
682+
fontSize: 12,
683+
color: _runningEntry != null ? Colors.green : Colors.grey,
684+
fontWeight: FontWeight.bold,
685+
),
686+
),
687+
),
688+
],
689+
),
690+
const Divider(),
691+
if (_runningEntry != null) ...[
692+
_buildInfoRow('Gestartet', _formatDateTime(_runningEntry!.start)),
693+
_buildInfoRow('Laufzeit', _formatDuration(DateTime.now().difference(_runningEntry!.start))),
694+
if (_runningEntry!.pauses.isNotEmpty)
695+
_buildInfoRow('Pausen', '${_runningEntry!.pauses.length}'),
696+
] else ...[
697+
const Text(
698+
'Keine laufende Arbeitszeit',
699+
style: TextStyle(color: Colors.grey),
700+
),
701+
const SizedBox(height: 8),
702+
Container(
703+
padding: const EdgeInsets.all(8),
704+
decoration: BoxDecoration(
705+
color: context.warningBackground,
706+
borderRadius: BorderRadius.circular(8),
707+
),
708+
child: Row(
709+
children: [
710+
Icon(Icons.info, size: 16, color: context.warningForeground),
711+
const SizedBox(width: 8),
712+
Expanded(
713+
child: Text(
714+
'Bei ENTER-Event wird neue Arbeitszeit gestartet, '
715+
'aber nur wenn keine bereits läuft!',
716+
style: TextStyle(fontSize: 11, color: context.warningForeground),
717+
),
718+
),
719+
],
720+
),
721+
),
722+
],
723+
if (_lastSyncResult != null) ...[
724+
const Divider(),
725+
_buildInfoRow('Letzter Sync', '$_lastSyncResult Events verarbeitet'),
726+
],
727+
],
728+
),
729+
),
730+
);
731+
}
732+
605733
Widget _buildEventQueueCard() {
606734
return Card(
607735
child: Padding(
@@ -850,6 +978,16 @@ class _GeofenceDebugScreenState extends ConsumerState<GeofenceDebugScreen> {
850978
],
851979
),
852980
const SizedBox(height: 12),
981+
// Primary Action: Force Sync
982+
FilledButton.icon(
983+
onPressed: _forceSyncNow,
984+
icon: const Icon(Icons.sync),
985+
label: const Text('Events jetzt verarbeiten'),
986+
style: FilledButton.styleFrom(
987+
minimumSize: const Size(double.infinity, 44),
988+
),
989+
),
990+
const SizedBox(height: 12),
853991
Wrap(
854992
spacing: 8,
855993
runSpacing: 8,
@@ -866,12 +1004,14 @@ class _GeofenceDebugScreenState extends ConsumerState<GeofenceDebugScreen> {
8661004
OutlinedButton.icon(
8671005
onPressed: () async {
8681006
// Simulate ENTER event
1007+
final zones = ref.read(geofenceZonesProvider);
1008+
final zoneId = zones.isNotEmpty ? zones.first.id : 'debug_test';
8691009
await GeofenceEventQueue.enqueue(GeofenceEventData(
870-
zoneId: 'debug_test',
1010+
zoneId: zoneId,
8711011
event: GeofenceEvent.enter,
8721012
timestamp: DateTime.now(),
8731013
));
874-
_addLog('Test ENTER Event erstellt');
1014+
_addLog('Test ENTER Event erstellt (Zone: $zoneId)');
8751015
await _loadEventQueue();
8761016
},
8771017
icon: const Icon(Icons.login, size: 18),
@@ -880,12 +1020,14 @@ class _GeofenceDebugScreenState extends ConsumerState<GeofenceDebugScreen> {
8801020
OutlinedButton.icon(
8811021
onPressed: () async {
8821022
// Simulate EXIT event
1023+
final zones = ref.read(geofenceZonesProvider);
1024+
final zoneId = zones.isNotEmpty ? zones.first.id : 'debug_test';
8831025
await GeofenceEventQueue.enqueue(GeofenceEventData(
884-
zoneId: 'debug_test',
1026+
zoneId: zoneId,
8851027
event: GeofenceEvent.exit,
8861028
timestamp: DateTime.now(),
8871029
));
888-
_addLog('Test EXIT Event erstellt');
1030+
_addLog('Test EXIT Event erstellt (Zone: $zoneId)');
8891031
await _loadEventQueue();
8901032
},
8911033
icon: const Icon(Icons.logout, size: 18),
@@ -983,6 +1125,15 @@ class _GeofenceDebugScreenState extends ConsumerState<GeofenceDebugScreen> {
9831125
}
9841126
return '${(meters / 1000).toStringAsFixed(1)}km';
9851127
}
1128+
1129+
String _formatDuration(Duration duration) {
1130+
final hours = duration.inHours;
1131+
final minutes = duration.inMinutes.remainder(60);
1132+
if (hours > 0) {
1133+
return '${hours}h ${minutes}m';
1134+
}
1135+
return '${minutes}m';
1136+
}
9861137
}
9871138

9881139
class _LogEntry {

pubspec.yaml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
name: time_tracker
22
description: Zeiterfassung mit Geofence, Urlaub, Feiertagen & ICS-Sync
3-
version: 0.1.0-beta.25+25
3+
version: 0.1.0-beta.26+26
44
publish_to: 'none'
55

66
environment:

0 commit comments

Comments
 (0)