273 lines
8.1 KiB
Dart
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),
|
|
),
|
|
],
|
|
),
|
|
);
|
|
}
|
|
}
|