372 lines
13 KiB
Dart
372 lines
13 KiB
Dart
import 'package:firebase_core/firebase_core.dart';
|
|
import 'package:flutter/material.dart';
|
|
import 'package:firebase_database/firebase_database.dart';
|
|
import 'package:cloud_firestore/cloud_firestore.dart';
|
|
import 'package:firebase_auth/firebase_auth.dart';
|
|
import 'wait_for_rfid_screen.dart';
|
|
|
|
class StartActivityScreen extends StatefulWidget {
|
|
const StartActivityScreen({super.key});
|
|
|
|
@override
|
|
State<StartActivityScreen> createState() => _StartActivityScreenState();
|
|
}
|
|
|
|
class _StartActivityScreenState extends State<StartActivityScreen> {
|
|
String? _selectedType;
|
|
final _lapController = TextEditingController(); // Untuk input jumlah putaran
|
|
final _calculatedDistanceController = TextEditingController(); // Untuk menampilkan jarak total
|
|
String _unit = 'KM';
|
|
|
|
// Variabel untuk menghitung waktu setiap lap
|
|
DateTime lapStartTime = DateTime.now();
|
|
DateTime lapEndTime = DateTime.now();
|
|
double lapTimeInSeconds = 0;
|
|
double speed = 0.0; // Kecepatan dalam m/s
|
|
|
|
void _showError(String message) {
|
|
showDialog(
|
|
context: context,
|
|
builder: (context) => AlertDialog(
|
|
title: const Text("Error", style: TextStyle(color: Colors.red)),
|
|
content: Text(message),
|
|
actions: [
|
|
TextButton(
|
|
onPressed: () => Navigator.pop(context),
|
|
child: const Text("OK", style: TextStyle(color: Colors.blue)),
|
|
)
|
|
],
|
|
),
|
|
);
|
|
}
|
|
|
|
// Fungsi untuk memulai timer saat lap dimulai
|
|
void startLapTimer() {
|
|
lapStartTime = DateTime.now(); // Mulai timer dengan DateTime
|
|
}
|
|
|
|
// Fungsi untuk menghentikan timer saat lap selesai dan menghitung kecepatan
|
|
void endLapTimer(double lapDistance, String uidTag) {
|
|
lapEndTime = DateTime.now(); // Hentikan timer dengan DateTime
|
|
lapTimeInSeconds = lapEndTime.difference(lapStartTime).inSeconds.toDouble(); // Hitung waktu dalam detik
|
|
speed = lapDistance / lapTimeInSeconds; // Hitung kecepatan dalam meter per detik
|
|
|
|
// Simpan kecepatan per lap di Firebase
|
|
saveSpeedToFirebase(uidTag, speed);
|
|
}
|
|
|
|
// Fungsi untuk menyimpan kecepatan per lap ke Firebase (RTDB)
|
|
void saveSpeedToFirebase(String uidTag, double speed) {
|
|
final user = FirebaseAuth.instance.currentUser;
|
|
|
|
if (user == null) {
|
|
print("User tidak ditemukan");
|
|
return;
|
|
}
|
|
|
|
try {
|
|
final db = FirebaseDatabase.instanceFor(
|
|
app: Firebase.app(),
|
|
databaseURL: "https://ta-running-default-rtdb.asia-southeast1.firebasedatabase.app",
|
|
);
|
|
final ref = db.ref("activities/$uidTag");
|
|
|
|
// Simpan kecepatan per lap
|
|
ref.child('kecepatan_lap').push().set({
|
|
'speed': speed,
|
|
'timestamp': DateTime.now().toIso8601String(),
|
|
});
|
|
|
|
print("Kecepatan lap $uidTag berhasil disimpan: $speed m/s");
|
|
} catch (e) {
|
|
print("❌ Gagal menyimpan kecepatan lap: $e");
|
|
}
|
|
}
|
|
|
|
// Fungsi untuk mengupdate jarak dan kecepatan setiap lap
|
|
void _updateDistance() {
|
|
final laps = int.tryParse(_lapController.text);
|
|
if (laps != null && laps >= 3) {
|
|
final totalDistance = laps * 400; // 400 meter per putaran
|
|
_calculatedDistanceController.text = totalDistance.toString();
|
|
} else if (laps != null && laps < 3) {
|
|
_calculatedDistanceController.text = '';
|
|
_showError("Jumlah putaran minimal adalah 3 (1200m).");
|
|
} else {
|
|
_calculatedDistanceController.text = '';
|
|
}
|
|
}
|
|
|
|
// Fungsi untuk submit aktivitas ke Firebase
|
|
void _submit() {
|
|
if (_selectedType == 'Lintasan') {
|
|
final laps = int.tryParse(_lapController.text);
|
|
|
|
if (laps == null || laps < 3) {
|
|
_showError("Masukkan jumlah putaran yang valid (minimal 3).");
|
|
return;
|
|
}
|
|
|
|
final totalDistance = laps * 400; // 400 meter per putaran
|
|
|
|
_calculatedDistanceController.text = totalDistance.toString();
|
|
|
|
submitToFirebase(
|
|
jarak: totalDistance.toDouble(),
|
|
jenisAktivitas: 'lintasan',
|
|
satuan: 'M',
|
|
putaran: laps,
|
|
);
|
|
} else if (_selectedType == 'Non-Lintasan') {
|
|
final jarak = double.tryParse(_lapController.text);
|
|
|
|
if (jarak == null || jarak <= 0) {
|
|
_showError("Masukkan jarak yang valid.");
|
|
return;
|
|
}
|
|
|
|
submitToFirebase(
|
|
jarak: jarak,
|
|
jenisAktivitas: 'non-lintasan',
|
|
satuan: _unit,
|
|
);
|
|
} else {
|
|
_showError("Pilih jenis aktivitas terlebih dahulu.");
|
|
}
|
|
}
|
|
|
|
// Fungsi untuk submit data ke Firebase
|
|
void submitToFirebase({
|
|
required double jarak,
|
|
required String jenisAktivitas,
|
|
required String satuan,
|
|
int? putaran,
|
|
}) async {
|
|
final user = FirebaseAuth.instance.currentUser;
|
|
|
|
if (user == null) {
|
|
print("User tidak ditemukan");
|
|
return;
|
|
}
|
|
|
|
try {
|
|
final userDoc = await FirebaseFirestore.instance
|
|
.collection("users")
|
|
.doc(user.uid)
|
|
.get();
|
|
|
|
final userData = userDoc.data();
|
|
if (userData == null || !userData.containsKey("idGelang")) {
|
|
ScaffoldMessenger.of(context).showSnackBar(
|
|
const SnackBar(content: Text("Silakan daftarkan gelang RFID terlebih dahulu.")),
|
|
);
|
|
return;
|
|
}
|
|
|
|
final uidTag = userData["idGelang"];
|
|
|
|
final db = FirebaseDatabase.instanceFor(
|
|
app: Firebase.app(),
|
|
databaseURL: "https://ta-running-default-rtdb.asia-southeast1.firebasedatabase.app",
|
|
);
|
|
final ref = db.ref("activities/$uidTag");
|
|
|
|
await ref.set({
|
|
'uid_apk': user.uid,
|
|
'jarak': jarak,
|
|
'satuan': satuan,
|
|
'putaran': putaran,
|
|
'type': jenisAktivitas,
|
|
'status': 'waiting_for_rfid',
|
|
'timestamp': DateTime.now().toIso8601String(),
|
|
});
|
|
|
|
print("✅ Data aktivitas dikirim ke Firebase dengan UID tag: $uidTag");
|
|
|
|
Navigator.pushReplacement(
|
|
context,
|
|
MaterialPageRoute(
|
|
builder: (_) => WaitForRfidScreen(uidTag: uidTag),
|
|
),
|
|
);
|
|
} catch (e) {
|
|
print("❌ Gagal mengirim aktivitas: $e");
|
|
ScaffoldMessenger.of(context).showSnackBar(
|
|
SnackBar(content: Text("Gagal mengirim aktivitas: $e")),
|
|
);
|
|
}
|
|
}
|
|
|
|
@override
|
|
Widget build(BuildContext context) {
|
|
return Scaffold(
|
|
backgroundColor: Colors.white,
|
|
appBar: PreferredSize(
|
|
preferredSize: const Size.fromHeight(80),
|
|
child: Container(
|
|
decoration: const BoxDecoration(
|
|
color: Colors.blue,
|
|
borderRadius: BorderRadius.only(
|
|
bottomLeft: Radius.circular(20),
|
|
bottomRight: Radius.circular(20),
|
|
),
|
|
),
|
|
padding: const EdgeInsets.only(top: 40, left: 16, right: 16),
|
|
child: Row(
|
|
children: [
|
|
if (_selectedType != null)
|
|
IconButton(
|
|
icon: const Icon(Icons.arrow_back, color: Colors.white),
|
|
onPressed: () => setState(() => _selectedType = null),
|
|
)
|
|
else
|
|
const SizedBox(width: 48),
|
|
Expanded(
|
|
child: Center(
|
|
child: Text(
|
|
"Start Activity",
|
|
style: const TextStyle(
|
|
color: Colors.white,
|
|
fontSize: 28,
|
|
fontWeight: FontWeight.bold,
|
|
),
|
|
),
|
|
),
|
|
),
|
|
const SizedBox(width: 48),
|
|
],
|
|
),
|
|
),
|
|
),
|
|
body: SingleChildScrollView(
|
|
padding: const EdgeInsets.symmetric(horizontal: 24, vertical: 32),
|
|
child: _selectedType == null
|
|
? Column(
|
|
children: [
|
|
const SizedBox(height: 70),
|
|
Image.asset(
|
|
'images/run.png',
|
|
width: 100,
|
|
height: 100,
|
|
),
|
|
const SizedBox(height: 80),
|
|
const Text(
|
|
'Pilih Jenis Aktivitas',
|
|
style: TextStyle(fontSize: 28, fontWeight: FontWeight.w800),
|
|
textAlign: TextAlign.center,
|
|
),
|
|
const SizedBox(height: 28),
|
|
Row(
|
|
children: [
|
|
Expanded(
|
|
child: ElevatedButton(
|
|
onPressed: () => setState(() => _selectedType = 'Lintasan'),
|
|
style: ElevatedButton.styleFrom(
|
|
backgroundColor: _selectedType == 'Lintasan' ? Colors.blueAccent : Colors.white,
|
|
side: const BorderSide(color: Colors.blueAccent),
|
|
foregroundColor: _selectedType == 'Lintasan' ? Colors.white : Colors.blueAccent,
|
|
shape: RoundedRectangleBorder(borderRadius: BorderRadius.circular(10)),
|
|
padding: const EdgeInsets.symmetric(vertical: 14),
|
|
),
|
|
child: const Text('LINTASAN'),
|
|
),
|
|
),
|
|
const SizedBox(width: 12),
|
|
Expanded(
|
|
child: ElevatedButton(
|
|
onPressed: () => setState(() => _selectedType = 'Non-Lintasan'),
|
|
style: ElevatedButton.styleFrom(
|
|
backgroundColor: _selectedType == 'Non-Lintasan' ? Colors.blueAccent : Colors.white,
|
|
side: const BorderSide(color: Colors.blueAccent),
|
|
foregroundColor: _selectedType == 'Non-Lintasan' ? Colors.white : Colors.blueAccent,
|
|
shape: RoundedRectangleBorder(borderRadius: BorderRadius.circular(10)),
|
|
padding: const EdgeInsets.symmetric(vertical: 14),
|
|
),
|
|
child: const Text('NON-LINTASAN'),
|
|
),
|
|
),
|
|
],
|
|
),
|
|
],
|
|
)
|
|
: Column(
|
|
children: [
|
|
const SizedBox(height: 70),
|
|
Image.asset(
|
|
'images/run.png',
|
|
width: 100,
|
|
height: 100,
|
|
fit: BoxFit.contain,
|
|
),
|
|
const SizedBox(height: 80),
|
|
Text(
|
|
_selectedType!,
|
|
style: const TextStyle(fontSize: 28, fontWeight: FontWeight.w800),
|
|
),
|
|
const SizedBox(height: 20),
|
|
if (_selectedType == 'Lintasan') ...[
|
|
TextField(
|
|
controller: _lapController,
|
|
keyboardType: TextInputType.number,
|
|
decoration: const InputDecoration(
|
|
labelText: 'Jumlah Putaran (400M/Putaran)',
|
|
border: OutlineInputBorder(),
|
|
),
|
|
onChanged: (text) {
|
|
_updateDistance(); // Update jarak total setiap kali jumlah putaran diubah
|
|
},
|
|
),
|
|
const SizedBox(height: 12),
|
|
TextField(
|
|
controller: _calculatedDistanceController,
|
|
enabled: false, // Disable editing
|
|
decoration: const InputDecoration(
|
|
labelText: 'Jarak Total (M)',
|
|
border: OutlineInputBorder(),
|
|
),
|
|
),
|
|
] else ...[
|
|
Row(
|
|
children: [
|
|
Expanded(
|
|
child: TextField(
|
|
controller: _lapController, // Menggunakan lapController untuk Non-Lintasan
|
|
keyboardType: TextInputType.number,
|
|
decoration: const InputDecoration(
|
|
labelText: 'Jarak',
|
|
border: OutlineInputBorder(),
|
|
),
|
|
),
|
|
),
|
|
const SizedBox(width: 12),
|
|
DropdownButton<String>(
|
|
value: _unit,
|
|
items: const [
|
|
DropdownMenuItem(value: 'KM', child: Text('KM')),
|
|
DropdownMenuItem(value: 'M', child: Text('M')),
|
|
],
|
|
onChanged: (value) => setState(() => _unit = value!),
|
|
),
|
|
],
|
|
),
|
|
],
|
|
const SizedBox(height: 20),
|
|
ElevatedButton(
|
|
onPressed: _submit,
|
|
style: ElevatedButton.styleFrom(
|
|
backgroundColor: Colors.blueAccent,
|
|
padding: const EdgeInsets.symmetric(horizontal: 24, vertical: 12),
|
|
shape: RoundedRectangleBorder(borderRadius: BorderRadius.circular(12)),
|
|
),
|
|
child: const Text(
|
|
'Submit dan Scan RFID',
|
|
style: TextStyle(fontSize: 16, color: Colors.white),
|
|
),
|
|
),
|
|
],
|
|
),
|
|
),
|
|
);
|
|
}
|
|
}
|