270 lines
9.2 KiB
Dart
270 lines
9.2 KiB
Dart
import 'package:flutter/material.dart';
|
|
import 'package:intl/intl.dart';
|
|
import '../widgets/responsive_layout.dart';
|
|
|
|
class HistoryDetailScreen extends StatelessWidget {
|
|
final Map<String, dynamic> historyItem;
|
|
|
|
const HistoryDetailScreen({super.key, required this.historyItem});
|
|
|
|
String _formatTime(int seconds) {
|
|
bool isNegative = seconds < 0;
|
|
int absSecs = seconds.abs();
|
|
int m = absSecs ~/ 60;
|
|
int s = absSecs % 60;
|
|
String result = "${m.toString().padLeft(2, '0')}:${s.toString().padLeft(2, '0')}";
|
|
return isNegative ? "+$result" : result;
|
|
}
|
|
|
|
String _formatDate(String dateStr) {
|
|
try {
|
|
DateTime dt = DateTime.parse(dateStr);
|
|
return DateFormat('dd MMMM yyyy, HH:mm', 'id_ID').format(dt);
|
|
} catch (e) {
|
|
return dateStr;
|
|
}
|
|
}
|
|
|
|
@override
|
|
Widget build(BuildContext context) {
|
|
final isDark = Theme.of(context).brightness == Brightness.dark;
|
|
|
|
// Parsing Data
|
|
double totalDistance = double.tryParse(historyItem['jarak'].toString().replaceAll(RegExp(r'[^0-9.]'), '')) ?? 0;
|
|
double traveledDistance = double.tryParse(historyItem['jarak_tempuh'].toString()) ?? 0;
|
|
double remainingDistance = totalDistance - traveledDistance;
|
|
if (remainingDistance < 0) remainingDistance = 0;
|
|
|
|
int timeSpentSeconds = int.tryParse(historyItem['waktu_tempuh'].toString()) ?? 0;
|
|
int targetTimeSeconds = int.tryParse(historyItem['target_waktu'].toString()) ?? 0;
|
|
// Sisa waktu = Target - Terpakai.
|
|
// Jika positif (misal 10 - 8 = 2) -> Bagus.
|
|
// Jika negatif (misal 10 - 13 = -3) -> Telat, diformat jadi +03:00 di _formatTime.
|
|
int remainingTimeSeconds = targetTimeSeconds - timeSpentSeconds;
|
|
|
|
String speed = historyItem['kecepatan_rata_rata']?.toString() ?? "0";
|
|
String status = historyItem['status']?.toString().toUpperCase() ?? "GAGAL";
|
|
bool isSuccess = status == "BERHASIL";
|
|
|
|
return ResponsiveLayout(
|
|
child: Scaffold(
|
|
extendBodyBehindAppBar: true,
|
|
backgroundColor: Colors.transparent,
|
|
appBar: AppBar(
|
|
title: const Text(
|
|
"Detail Perjalanan",
|
|
style: TextStyle(fontWeight: FontWeight.bold, color: Colors.white),
|
|
),
|
|
centerTitle: true,
|
|
backgroundColor: Colors.transparent,
|
|
elevation: 0,
|
|
leading: IconButton(
|
|
icon: const Icon(Icons.arrow_back, color: Colors.white),
|
|
onPressed: () => Navigator.pop(context),
|
|
),
|
|
),
|
|
body: Container(
|
|
decoration: const BoxDecoration(
|
|
gradient: LinearGradient(
|
|
colors: [Color(0xFF1A237E), Color(0xFF121212)],
|
|
begin: Alignment.topCenter,
|
|
end: Alignment.bottomCenter,
|
|
),
|
|
),
|
|
child: SafeArea(
|
|
child: SingleChildScrollView(
|
|
padding: const EdgeInsets.all(24.0),
|
|
child: Column(
|
|
crossAxisAlignment: CrossAxisAlignment.stretch,
|
|
children: [
|
|
// CARD STATUS
|
|
Container(
|
|
padding: const EdgeInsets.all(24),
|
|
decoration: BoxDecoration(
|
|
gradient: LinearGradient(
|
|
colors: isSuccess
|
|
? [const Color(0xFF11998e), const Color(0xFF38ef7d)]
|
|
: [const Color(0xFFcb2d3e), const Color(0xFFef473a)],
|
|
begin: Alignment.topLeft,
|
|
end: Alignment.bottomRight,
|
|
),
|
|
borderRadius: BorderRadius.circular(20),
|
|
boxShadow: [
|
|
BoxShadow(
|
|
color: (isSuccess ? Colors.green : Colors.red).withOpacity(0.3),
|
|
blurRadius: 10,
|
|
offset: const Offset(0, 5),
|
|
)
|
|
]
|
|
),
|
|
child: Column(
|
|
children: [
|
|
Icon(
|
|
isSuccess ? Icons.check_circle_outline : Icons.error_outline,
|
|
size: 64,
|
|
color: Colors.white,
|
|
),
|
|
const SizedBox(height: 16),
|
|
Text(
|
|
isSuccess ? "MISI BERHASIL" : "MISI GAGAL",
|
|
style: const TextStyle(
|
|
color: Colors.white,
|
|
fontSize: 24,
|
|
fontWeight: FontWeight.bold,
|
|
letterSpacing: 1.2,
|
|
),
|
|
),
|
|
const SizedBox(height: 8),
|
|
Text(
|
|
_formatDate(historyItem['tanggal'] ?? ''),
|
|
style: const TextStyle(
|
|
color: Colors.white70,
|
|
fontSize: 14,
|
|
),
|
|
),
|
|
],
|
|
),
|
|
),
|
|
const SizedBox(height: 24),
|
|
|
|
// INFORMASI RUTE
|
|
_buildSectionHeader("Informasi Rute", isDark),
|
|
_buildDetailTile(
|
|
icon: Icons.directions_bike,
|
|
title: "Nama Rute",
|
|
value: historyItem['nama_rute'] ?? '-',
|
|
isDark: isDark,
|
|
),
|
|
_buildDetailTile(
|
|
icon: Icons.straighten,
|
|
title: "Jarak Rute",
|
|
value: "${historyItem['jarak'] ?? '-'} Km",
|
|
isDark: isDark,
|
|
),
|
|
_buildDetailTile(
|
|
icon: Icons.terrain,
|
|
title: "Kondisi Medan",
|
|
value: historyItem['kondisi_medan'] ?? '-',
|
|
isDark: isDark,
|
|
),
|
|
_buildDetailTile(
|
|
icon: Icons.hourglass_top,
|
|
title: "Target Waktu Perjalanan",
|
|
value: targetTimeSeconds > 0 ? _formatTime(targetTimeSeconds) : "-",
|
|
isDark: isDark,
|
|
),
|
|
const SizedBox(height: 24),
|
|
|
|
// DETAIL METRIK
|
|
_buildSectionHeader("Metrik Perjalanan", isDark),
|
|
_buildDetailTile(
|
|
icon: Icons.straighten,
|
|
title: "Jarak yang ditempuh",
|
|
value: "${traveledDistance.toStringAsFixed(2)} km",
|
|
subtitle: "Sisa Jarak : ${remainingDistance.toStringAsFixed(2)} km",
|
|
isDark: isDark,
|
|
),
|
|
_buildDetailTile(
|
|
icon: Icons.timer,
|
|
title: "Waktu yang ditempuh",
|
|
value: _formatTime(timeSpentSeconds),
|
|
subtitle: "Sisa Waktu : ${_formatTime(remainingTimeSeconds)}",
|
|
subtitleColor: remainingTimeSeconds < 0 ? Colors.redAccent : Colors.blueAccent,
|
|
isDark: isDark,
|
|
),
|
|
_buildDetailTile(
|
|
icon: Icons.speed,
|
|
title: "Kecepatan Rata-rata",
|
|
value: "$speed km/jam",
|
|
isDark: isDark,
|
|
),
|
|
],
|
|
),
|
|
),
|
|
),
|
|
),
|
|
),
|
|
);
|
|
}
|
|
|
|
Widget _buildSectionHeader(String title, bool isDark) {
|
|
return Padding(
|
|
padding: const EdgeInsets.only(bottom: 12.0, left: 4.0),
|
|
child: Text(
|
|
title,
|
|
style: const TextStyle(
|
|
fontSize: 16,
|
|
fontWeight: FontWeight.bold,
|
|
color: Colors.white70,
|
|
),
|
|
),
|
|
);
|
|
}
|
|
|
|
Widget _buildDetailTile({
|
|
required IconData icon,
|
|
required String title,
|
|
required String value,
|
|
String? subtitle,
|
|
Color? subtitleColor,
|
|
required bool isDark,
|
|
}) {
|
|
return Container(
|
|
margin: const EdgeInsets.only(bottom: 12),
|
|
padding: const EdgeInsets.all(16),
|
|
decoration: BoxDecoration(
|
|
color: Colors.white.withOpacity(0.05),
|
|
borderRadius: BorderRadius.circular(15),
|
|
border: Border.all(color: Colors.white.withOpacity(0.1)),
|
|
),
|
|
child: Row(
|
|
children: [
|
|
Container(
|
|
padding: const EdgeInsets.all(10),
|
|
decoration: BoxDecoration(
|
|
color: Colors.blueAccent.withOpacity(0.2),
|
|
shape: BoxShape.circle,
|
|
),
|
|
child: Icon(icon, color: Colors.blueAccent, size: 24),
|
|
),
|
|
const SizedBox(width: 16),
|
|
Expanded(
|
|
child: Column(
|
|
crossAxisAlignment: CrossAxisAlignment.start,
|
|
children: [
|
|
Text(
|
|
title,
|
|
style: const TextStyle(
|
|
fontSize: 13,
|
|
color: Colors.white54,
|
|
),
|
|
),
|
|
const SizedBox(height: 4),
|
|
Text(
|
|
value,
|
|
style: const TextStyle(
|
|
fontSize: 18,
|
|
fontWeight: FontWeight.bold,
|
|
color: Colors.white,
|
|
),
|
|
),
|
|
if (subtitle != null) ...[
|
|
const SizedBox(height: 4),
|
|
Text(
|
|
subtitle,
|
|
style: TextStyle(
|
|
fontSize: 13,
|
|
color: subtitleColor ?? Colors.blueAccent,
|
|
fontWeight: FontWeight.w500,
|
|
),
|
|
),
|
|
]
|
|
],
|
|
),
|
|
),
|
|
],
|
|
),
|
|
);
|
|
}
|
|
}
|