diff --git a/lib/main.dart b/lib/main.dart index 4900958..805c860 100644 --- a/lib/main.dart +++ b/lib/main.dart @@ -1,4 +1,6 @@ import 'package:flutter/material.dart'; +import 'dart:async'; +import 'package:flutter/services.dart'; void main() { runApp(const MyApp()); @@ -27,12 +29,179 @@ class MyHomePage extends StatefulWidget { } class _MyHomePageState extends State { + Timer? _timer; + DateTime? _startTime; + DateTime? _pauseTime; + Duration _elapsedTime = Duration.zero; + bool _isRunning = false; + List _laps = []; + final FocusNode _focusNode = FocusNode(); + + void _startTimer() { + final now = DateTime.now(); + if (_pauseTime != null) { + // Resuming from pause + final pausedDuration = now.difference(_pauseTime!); + _startTime = _startTime?.add(pausedDuration); + _pauseTime = null; + } else { + // Starting fresh + _startTime = now; + _elapsedTime = Duration.zero; + _laps = []; + } + + _timer = Timer.periodic(const Duration(milliseconds: 100), (timer) { + setState(() { + _elapsedTime = DateTime.now().difference(_startTime!); + }); + }); + + setState(() { + _isRunning = true; + }); + } + + void _pauseTimer() { + _timer?.cancel(); + _pauseTime = DateTime.now(); + setState(() { + _isRunning = false; + }); + } + + void _stopTimer() { + _timer?.cancel(); + setState(() { + _isRunning = false; + _startTime = null; + _pauseTime = null; + _elapsedTime = Duration.zero; + }); + } + + void _recordLap() { + if (_isRunning) { + final now = DateTime.now(); + setState(() { + _laps.add(now.toIso8601String()); + }); + } + } + + String _formatDuration(Duration duration) { + // Format the duration as ISO 8601 duration format: PT[hours]H[minutes]M[seconds]S + final hours = duration.inHours; + final minutes = duration.inMinutes.remainder(60); + final seconds = duration.inSeconds.remainder(60); + final milliseconds = duration.inMilliseconds.remainder(1000); + + return 'PT${hours}H${minutes}M${seconds}.${milliseconds}S'; + } + + void _handleKeyEvent(RawKeyEvent event) { + if (event is RawKeyDownEvent) { + if (event.logicalKey == LogicalKeyboardKey.space) { + // Space: Start if paused, Lap if running + if (_isRunning) { + _recordLap(); + } else { + _startTimer(); + } + } else if (event.logicalKey == LogicalKeyboardKey.enter) { + // Enter: Stop if paused, Pause if running + if (_isRunning) { + _pauseTimer(); + } else { + _stopTimer(); + } + } + } + } + + @override + void initState() { + super.initState(); + _focusNode.requestFocus(); + } + + @override + void dispose() { + _timer?.cancel(); + _focusNode.dispose(); + super.dispose(); + } + @override Widget build(BuildContext context) { - return Scaffold( - appBar: AppBar( - backgroundColor: Theme.of(context).colorScheme.inversePrimary, - title: Text(widget.title), + return RawKeyboardListener( + focusNode: _focusNode, + onKey: _handleKeyEvent, + autofocus: true, + child: Scaffold( + appBar: AppBar( + backgroundColor: Theme.of(context).colorScheme.inversePrimary, + title: Text(widget.title), + ), + body: Center( + child: Column( + mainAxisAlignment: MainAxisAlignment.center, + children: [ + Text( + 'Time Elapsed:', + style: Theme.of(context).textTheme.headlineSmall, + ), + const SizedBox(height: 10), + Text( + _formatDuration(_elapsedTime), + style: Theme.of(context).textTheme.headlineMedium, + ), + const SizedBox(height: 30), + Row( + mainAxisAlignment: MainAxisAlignment.center, + children: [ + ElevatedButton( + onPressed: _isRunning ? _pauseTimer : _startTimer, + child: Text(_isRunning ? 'Pause' : 'Start'), + ), + const SizedBox(width: 20), + ElevatedButton( + onPressed: _stopTimer, + child: const Text('Stop'), + ), + const SizedBox(width: 20), + ElevatedButton( + onPressed: _recordLap, + child: const Text('Lap'), + ), + ], + ), + const SizedBox(height: 10), + Text( + 'Keyboard: SPACE for Start/Lap, ENTER for Pause/Stop', + style: Theme.of(context).textTheme.bodySmall, + ), + const SizedBox(height: 20), + if (_laps.isNotEmpty) ...[ + Text( + 'Laps:', + style: Theme.of(context).textTheme.titleLarge, + ), + const SizedBox(height: 10), + Expanded( + child: ListView.builder( + itemCount: _laps.length, + itemBuilder: (context, index) { + return ListTile( + title: Text('Lap ${index + 1}: ${_laps[index]}'), + ); + }, + ), + ), + ], + ], + ), + ), ), ); }