Implement everything
Thanks claude!"
This commit is contained in:
171
lib/main.dart
171
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,13 +29,180 @@ class MyHomePage extends StatefulWidget {
|
||||
}
|
||||
|
||||
class _MyHomePageState extends State<MyHomePage> {
|
||||
Timer? _timer;
|
||||
DateTime? _startTime;
|
||||
DateTime? _pauseTime;
|
||||
Duration _elapsedTime = Duration.zero;
|
||||
bool _isRunning = false;
|
||||
List<String> _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(
|
||||
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]}'),
|
||||
);
|
||||
},
|
||||
),
|
||||
),
|
||||
],
|
||||
],
|
||||
),
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
Reference in New Issue
Block a user