timetracker/lib/screens/home_screen.dart
2025-04-09 21:59:26 +02:00

273 lines
8.1 KiB
Dart

import 'dart:async';
import 'dart:developer';
import 'package:flutter/cupertino.dart' show CupertinoColors;
import 'package:flutter/material.dart';
import 'package:flutter_platform_widgets/flutter_platform_widgets.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();
class HomeScreen extends StatefulWidget {
const HomeScreen({super.key});
@override
State<HomeScreen> createState() => _HomeScreenState();
}
class _HomeScreenState extends State<HomeScreen> {
Timer? _timer;
Duration _elapsedTime = Duration.zero;
@override
void initState() {
super.initState();
final initialTracking = context.read<TimeTrackingService>().currentTracking;
if (initialTracking != null) {
_startTimer(unixSecondsToDateTime(initialTracking.startTime));
}
}
@override
void dispose() {
_stopTimer();
super.dispose();
}
void _startTimer(DateTime startTime) {
_stopTimer();
_calculateElapsedTime(startTime);
_timer = Timer.periodic(const Duration(seconds: 1), (_) {
final currentTracking =
context.read<TimeTrackingService>().currentTracking;
if (currentTracking != null) {
_calculateElapsedTime(unixSecondsToDateTime(currentTracking.startTime));
} else {
_stopTimer();
}
});
}
void _stopTimer() {
if (_timer != null) {
_timer!.cancel();
_timer = null;
}
}
void _calculateElapsedTime(DateTime startTime) {
final now = DateTime.now();
final duration = now.difference(startTime);
if (mounted) {
setState(() {
_elapsedTime = duration.isNegative ? Duration.zero : duration;
});
}
}
String _formatDuration(Duration duration) {
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";
}
void _checkAndManageTimerState(TimeEntry? currentTracking) {
if (currentTracking != null) {
if (_timer == null || !_timer!.isActive) {
log("Timer wird gestartet...");
_startTimer(unixSecondsToDateTime(currentTracking.startTime));
}
} else {
if (_timer != null && _timer!.isActive) {
log("Timer wird gestoppt...");
_stopTimer();
WidgetsBinding.instance.addPostFrameCallback((_) {
if (mounted && _elapsedTime != Duration.zero) {
setState(() {
_elapsedTime = Duration.zero;
});
}
});
}
}
}
@override
Widget build(BuildContext context) {
final timeService = context.watch<TimeTrackingService>();
final currentTracking = timeService.currentTracking;
_checkAndManageTimerState(currentTracking);
return PlatformScaffold(
appBar: PlatformAppBar(title: PlatformText('Tracking')),
body: _buildBodyContent(context, currentTracking, timeService),
);
}
Widget _buildBodyContent(
BuildContext context,
TimeEntry? currentTracking,
TimeTrackingService timeService,
) {
return Center(
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
children: <Widget>[
if (currentTracking != null)
_buildRunningTrackingInfo(context, currentTracking)
else
PlatformText('Kein aktives Tracking.'),
const SizedBox(height: 20),
PlatformElevatedButton(
onPressed: () async {
if (currentTracking != null) {
final success = await timeService.flStopTracking();
if (!success && context.mounted) {
_showPlatformFeedbackDialog(
context,
'Fehler',
'Fehler beim Stoppen des Trackings.',
);
}
} else {
_showPlatformTagSelection(context);
}
},
child: PlatformText(
currentTracking != null ? 'Tracking Stoppen' : 'Tracking Starten',
),
material:
(_, __) => MaterialElevatedButtonData(
style: ElevatedButton.styleFrom(
backgroundColor:
currentTracking != null ? Colors.red : Colors.green,
foregroundColor: Colors.white,
padding: const EdgeInsets.symmetric(
horizontal: 50,
vertical: 20,
),
textStyle: const TextStyle(fontSize: 20),
),
),
cupertino:
(_, __) => CupertinoElevatedButtonData(
color:
currentTracking != null
? CupertinoColors.destructiveRed
: CupertinoColors.activeGreen,
padding: const EdgeInsets.symmetric(
horizontal: 50,
vertical: 20,
),
),
),
],
),
);
}
void _showPlatformTagSelection(BuildContext context) {
final timeService = context.read<TimeTrackingService>();
final List<Widget> options = [
ListTile(
title: PlatformText('Ohne Tag'),
onTap: () {
Navigator.of(context).pop();
timeService.flStartTracking(0);
},
),
const Divider(height: 1),
...timeService.tags.map(
(tag) => ListTile(
title: PlatformText(tag.name),
onTap: () {
Navigator.of(context).pop();
timeService.flStartTracking(tag.id);
},
),
),
];
showPlatformModalSheet(
context: context,
material: MaterialModalSheetData(),
cupertino: CupertinoModalSheetData(),
builder:
(_) => Material(
child: SafeArea(
child: SingleChildScrollView(
child: Column(
mainAxisSize: MainAxisSize.min,
crossAxisAlignment: CrossAxisAlignment.stretch,
children: options,
),
),
),
),
);
}
Widget _buildRunningTrackingInfo(BuildContext context, TimeEntry entry) {
final tagName = entry.tagName ?? 'Ohne Tag';
return Column(
children: [
PlatformText(
_formatDuration(_elapsedTime),
style: platformThemeData(
context,
material:
(data) => data.textTheme.headlineMedium?.copyWith(
fontWeight: FontWeight.bold,
fontFeatures: const [FontFeature.tabularFigures()],
),
cupertino:
(data) => data.textTheme.navLargeTitleTextStyle.copyWith(
fontWeight: FontWeight.bold,
),
),
),
const SizedBox(height: 5),
PlatformText('Aktives Tracking: $tagName'),
PlatformText(
'Gestartet: ${DateFormat('HH:mm:ss').format(unixSecondsToDateTime(entry.startTime))}',
style: platformThemeData(
context,
material: (data) => data.textTheme.bodySmall,
cupertino: (data) => data.textTheme.actionSmallTextStyle,
),
),
],
);
}
void _showPlatformFeedbackDialog(
BuildContext context,
String title,
String message,
) {
showPlatformDialog(
context: context,
builder:
(_) => PlatformAlertDialog(
title: PlatformText(title),
content: PlatformText(message),
actions: <Widget>[
PlatformDialogAction(
child: PlatformText('OK'),
onPressed: () => Navigator.pop(context),
),
],
),
);
}
}