TKK__E32232246/lib/main.dart

370 lines
11 KiB
Dart

import 'package:flutter/material.dart';
import 'dart:convert';
import 'package:http/http.dart' as http;
import 'dart:async';
import 'package:intl/date_symbol_data_local.dart';
import 'riwayat_screen.dart';
void main() async {
WidgetsFlutterBinding.ensureInitialized();
await initializeDateFormatting('id_ID', null);
runApp(const MyApp());
}
class MyApp extends StatelessWidget {
const MyApp({super.key});
@override
Widget build(BuildContext context) {
return MaterialApp(
title: 'Health Monitor',
debugShowCheckedModeBanner: false,
theme: ThemeData.light().copyWith(
scaffoldBackgroundColor: const Color(0xFFF8F9FA),
),
home: const DashboardScreen(),
);
}
}
class DashboardScreen extends StatefulWidget {
const DashboardScreen({super.key});
@override
State<DashboardScreen> createState() => _DashboardScreenState();
}
class _DashboardScreenState extends State<DashboardScreen> {
int _currentIndex = 0;
int bpm = 0;
int spo2 = 0;
double temperature = 0.0;
int systolic = 0;
int diastolic = 0;
String lastUpdate = "Menunggu data...";
String statusMessage = "Menghubungkan ke server...";
// URL untuk mengambil data terbaru
final String fetchUrl = "http://10.105.126.239/iot/api.php?action=latest";
Timer? timer;
Future<void> fetchData() async {
setState(() => statusMessage = "Mengambil data...");
print("🔄 Fetching from: $fetchUrl"); // Debug Console
try {
final response = await http
.get(Uri.parse(fetchUrl), headers: {'Cache-Control': 'no-cache'})
.timeout(const Duration(seconds: 10));
print("📡 Status Code: ${response.statusCode}");
if (response.statusCode == 200) {
final data = json.decode(response.body);
print("📦 Response Body: ${response.body}"); // Debug
if (data['status'] == 'success') {
setState(() {
bpm = data['bpm'] ?? 0;
spo2 = data['spo2'] ?? 0;
temperature = (data['temperature'] ?? 0.0).toDouble();
systolic = data['systolic'] ?? 120;
diastolic = data['diastolic'] ?? 80;
lastUpdate = data['time'] ?? "Baru saja";
statusMessage = "✅ Data berhasil diambil";
});
} else {
setState(
() => statusMessage = "${data['message'] ?? 'Error dari server'}",
);
}
} else {
setState(
() => statusMessage = "❌ Server error (${response.statusCode})",
);
}
} catch (e) {
print("❌ Error: $e");
setState(() => statusMessage = "❌ Tidak bisa terhubung ke laptop");
}
}
@override
void initState() {
super.initState();
fetchData();
timer = Timer.periodic(const Duration(seconds: 4), (timer) {
fetchData();
});
}
@override
void dispose() {
timer?.cancel();
super.dispose();
}
@override
Widget build(BuildContext context) {
final List<Widget> screens = [
SingleChildScrollView(
padding: const EdgeInsets.all(16),
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
const Text(
"Halo, User! 👋",
style: TextStyle(fontSize: 26, fontWeight: FontWeight.bold),
),
const Text(
"Berikut adalah hasil kesehatan Anda.",
style: TextStyle(fontSize: 16, color: Colors.grey),
),
const SizedBox(height: 8),
Text(
statusMessage,
style: const TextStyle(fontSize: 14, color: Colors.grey),
),
const SizedBox(height: 24),
// Layout 2x2 Cards
Column(
children: [
Row(
children: [
Expanded(
child: _buildCard(
Icons.favorite,
Colors.red,
"Detak Jantung",
bpm.toString(),
"BPM",
bpm >= 60 && bpm <= 100,
),
),
const SizedBox(width: 12),
Expanded(
child: _buildCard(
Icons.air,
Colors.teal,
"SpO2",
spo2.toString(),
"%",
spo2 >= 95 && spo2 <= 100,
),
),
],
),
const SizedBox(height: 12),
Row(
children: [
Expanded(
child: _buildCard(
Icons.water_drop,
Colors.purple,
"Tekanan Darah",
"$systolic/$diastolic",
"mmHg",
systolic < 140 && diastolic < 90,
),
),
const SizedBox(width: 12),
Expanded(
child: _buildCard(
Icons.thermostat,
Colors.blue,
"Suhu Tubuh",
temperature.toStringAsFixed(1),
"°C",
temperature >= 36.0 && temperature <= 37.5,
),
),
],
),
],
),
const SizedBox(height: 24),
// Ringkasan Hasil
Container(
padding: const EdgeInsets.all(16),
decoration: BoxDecoration(
color: Colors.white,
borderRadius: BorderRadius.circular(12),
boxShadow: [
BoxShadow(
color: Colors.grey.withOpacity(0.1),
blurRadius: 10,
),
],
),
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
const Text(
"Ringkasan Hasil",
style: TextStyle(fontSize: 18, fontWeight: FontWeight.bold),
),
const SizedBox(height: 12),
_buildSummary("Detak Jantung", "$bpm BPM", Colors.red),
_buildSummary("SpO2", "$spo2%", Colors.teal),
_buildSummary(
"Suhu Tubuh",
"${temperature.toStringAsFixed(1)} °C",
Colors.blue,
),
_buildSummary(
"Tekanan Darah",
"$systolic/$diastolic mmHg",
Colors.purple,
),
],
),
),
],
),
),
const RiwayatScreen(),
];
return Scaffold(
backgroundColor: const Color(0xFFF8F9FA),
appBar: _currentIndex == 0
? AppBar(
backgroundColor: Colors.white,
elevation: 0,
title: Row(
children: const [
Icon(Icons.favorite, color: Colors.red, size: 28),
SizedBox(width: 8),
Text(
"Health Monitor",
style: TextStyle(
color: Colors.black87,
fontWeight: FontWeight.bold,
),
),
],
),
)
: null,
body: screens[_currentIndex],
bottomNavigationBar: BottomNavigationBar(
currentIndex: _currentIndex,
onTap: (index) {
setState(() => _currentIndex = index);
},
selectedItemColor: Colors.blue,
unselectedItemColor: Colors.grey,
items: const [
BottomNavigationBarItem(icon: Icon(Icons.home), label: "Beranda"),
BottomNavigationBarItem(icon: Icon(Icons.history), label: "Riwayat"),
],
),
);
}
Widget _buildCard(
IconData icon,
Color color,
String title,
String value,
String unit,
bool isNormal,
) {
final bool isSpo2 = title == "SpO2";
return Container(
padding: const EdgeInsets.all(12),
decoration: BoxDecoration(
color: Colors.white,
borderRadius: BorderRadius.circular(16),
boxShadow: [
BoxShadow(color: Colors.grey.withOpacity(0.08), blurRadius: 10),
],
),
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
children: [
CircleAvatar(
radius: 24,
backgroundColor: color.withOpacity(0.1),
child: isSpo2
? const Text(
"O₂",
style: TextStyle(
fontSize: 26,
fontWeight: FontWeight.bold,
color: Colors.teal,
),
)
: Icon(icon, color: color, size: 24),
),
const SizedBox(height: 12),
Text(
title,
style: const TextStyle(fontSize: 13, fontWeight: FontWeight.w500),
textAlign: TextAlign.center,
),
const SizedBox(height: 12),
Text(
value,
style: TextStyle(
fontSize: 26,
fontWeight: FontWeight.bold,
color: color,
),
),
Text(unit, style: const TextStyle(fontSize: 12)),
const SizedBox(height: 12),
Container(
padding: const EdgeInsets.symmetric(horizontal: 10, vertical: 6),
decoration: BoxDecoration(
color: isNormal
? Colors.green.withOpacity(0.1)
: Colors.orange.withOpacity(0.1),
borderRadius: BorderRadius.circular(20),
),
child: Text(
isNormal ? "Normal" : "Perlu Perhatian",
style: TextStyle(
color: isNormal ? Colors.green : Colors.orange,
fontWeight: FontWeight.w500,
fontSize: 11,
),
textAlign: TextAlign.center,
),
),
],
),
);
}
Widget _buildSummary(String label, String value, Color color) {
return Padding(
padding: const EdgeInsets.symmetric(vertical: 10),
child: Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: [
Row(
children: [
Icon(Icons.circle, size: 12, color: color),
const SizedBox(width: 10),
Text(label, style: const TextStyle(fontSize: 16)),
],
),
Text(
value,
style: const TextStyle(fontSize: 16, fontWeight: FontWeight.bold),
),
],
),
);
}
}