import 'package:flutter/material.dart'; // Import Flutter material package for UI widgets
import 'dart:async'; // Import async package for Timer
import 'package:heart_bpm/heart_bpm.dart'; // Import heart_bpm package for heart rate measurement
void main() {
runApp(const MyApp()); // Entry point: run the app with MyApp widget
}
class MyApp extends StatelessWidget {
// Main app widget, stateless
const MyApp({super.key}); // Constructor
@override
Widget build(BuildContext context) {
// Build method for widget tree
return MaterialApp(
title: 'Flutter Demo', // App title
debugShowCheckedModeBanner: false, // Hide debug banner
theme: ThemeData(
colorScheme: ColorScheme.fromSeed(
seedColor: Colors.deepPurple), // Set color scheme
useMaterial3: true, // Use Material 3 design
),
home: const MyHomePage(), // Set home page
);
}
}
class MyHomePage extends StatefulWidget {
// Home page widget, stateful
const MyHomePage({super.key}); // Constructor
@override
State<MyHomePage> createState() => _MyHomePageState(); // Create state
}
class _MyHomePageState extends State<MyHomePage> {
// State for MyHomePage
List<SensorValue> data = []; // List to store raw sensor values
int? bpmValue; // Current BPM value
int? finalBpm; // Final averaged BPM value
bool isMeasuring = false; // Flag to indicate if measurement is ongoing
Timer? timer; // Timer for countdown
int secondsLeft = 60; // Seconds left for measurement
List<int> bpmValues = []; // List to store BPM values during measurement
void startMeasurement() {
// Start measurement method
setState(() {
isMeasuring = true; // Set measuring flag
secondsLeft = 60; // Reset timer
bpmValues.clear(); // Clear previous BPM values
finalBpm = null; // Reset final BPM
});
timer = Timer.periodic(const Duration(seconds: 1), (t) {
// Start periodic timer
setState(() {
secondsLeft--; // Decrement seconds left
if (secondsLeft == 0) {
// If time is up
isMeasuring = false; // Stop measuring
timer?.cancel(); // Cancel timer
if (bpmValues.isNotEmpty) {
// If BPM values exist
finalBpm = (bpmValues.reduce((a, b) => a + b) / bpmValues.length)
.round(); // Calculate average BPM
}
}
});
});
}
void stopMeasurement() {
// Stop measurement method
timer?.cancel(); // Cancel timer
setState(() {
isMeasuring = false; // Stop measuring
secondsLeft = 60; // Reset timer
if (bpmValues.isNotEmpty) {
// If BPM values exist
finalBpm = (bpmValues.reduce((a, b) => a + b) / bpmValues.length)
.round(); // Calculate average BPM
}
});
}
@override
void dispose() {
// Dispose method to clean up timer
timer?.cancel(); // Cancel timer if running
super.dispose(); // Call super dispose
}
@override
Widget build(BuildContext context) {
// Build method for UI
return Scaffold(
body: Center(
child: Padding(
padding: const EdgeInsets.all(8.0), // Add padding
child: Column(
mainAxisSize: MainAxisSize.min, // Minimize column size
crossAxisAlignment: CrossAxisAlignment.center, // Center align
children: [
Text(
"Cover both the camera and flash with your finger", // Instruction text
style: Theme.of(context)
.textTheme
.headlineLarge
?.copyWith(fontWeight: FontWeight.bold), // Style text
textAlign: TextAlign.center, // Center text
),
const SizedBox(height: 22), // Spacing
if (isMeasuring) // Show timer if measuring
Text(
"Time left: $secondsLeft s", // Show seconds left
style: Theme.of(context).textTheme.titleLarge, // Style text
),
const SizedBox(height: 16), // Spacing
Row(
mainAxisSize: MainAxisSize.min, // Minimize row size
children: [
const Icon(
Icons.favorite, // Heart icon
size: 88, // Icon size
color: Colors.red, // Icon color
),
isMeasuring // If measuring, show HeartBPMDialog
? HeartBPMDialog(
context: context, // Pass context
onRawData: (value) {
// Callback for raw data
setState(() {
if (data.length == 100) {
// Keep only last 100 values
data.removeAt(0);
}
data.add(value); // Add new value
});
},
onBPM: (value) => setState(() {
// Callback for BPM value
bpmValue = value; // Set current BPM
if (value != null) {
bpmValues.add(value); // Add to BPM list
}
}),
child: Text(
bpmValue?.toString() ??
"-", // Show current BPM or dash
style: Theme.of(context)
.textTheme
.displayLarge
?.copyWith(
fontWeight: FontWeight.bold), // Style text
textAlign: TextAlign.center, // Center text
),
)
: Text(
finalBpm !=
null // If not measuring, show result or dash
? "Result: $finalBpm"
: "-",
style: Theme.of(context)
.textTheme
.displayLarge
?.copyWith(
fontWeight: FontWeight.bold), // Style text
textAlign: TextAlign.center, // Center text
),
],
),
const SizedBox(height: 32), // Spacing
isMeasuring // Show Stop button if measuring
? ElevatedButton(
style: ElevatedButton.styleFrom(
backgroundColor: Colors.green, // Button color
foregroundColor: Colors.white // Text color
),
onPressed: stopMeasurement, // Stop measurement on press
child: const Text("Stop"), // Button label
)
: ElevatedButton(
// Show Start button if not measuring
style: ElevatedButton.styleFrom(
backgroundColor: Colors.green, // Button color
foregroundColor: Colors.white // Text color
),
onPressed: startMeasurement, // Start measurement on press
child:
const Text("Start 1 min Measurement"), // Button label
),
],
),
),
),
);
}
}