MIF_E31231027/smart_cycling/lib/screens/home_screen.dart

879 lines
33 KiB
Dart

import 'dart:convert';
import 'package:flutter/material.dart';
import 'package:http/http.dart' as http;
import '../utils/api_config.dart';
import 'package:fl_chart/fl_chart.dart';
import 'search_route_screen.dart';
import 'history_screen.dart';
import 'history_detail_screen.dart';
import 'profile_screen.dart'; // Tambahkan import Profil
// Import session manager untuk nama dinamis
import '../utils/session_manager.dart';
import '../widgets/responsive_layout.dart';
class HomeScreen extends StatefulWidget {
const HomeScreen({super.key});
@override
State<HomeScreen> createState() => _HomeScreenState();
}
class _HomeScreenState extends State<HomeScreen> {
int _selectedIndex = 0;
final List<Widget> _pages;
_HomeScreenState() : _pages = [] {
_pages.addAll([
HomeDashboardContent(onPlanRoute: () => _onItemTapped(1)),
const SearchRouteScreen(),
const HistoryScreen(),
const ProfileScreen(),
]);
}
void _onItemTapped(int index) {
setState(() {
_selectedIndex = index;
});
}
@override
Widget build(BuildContext context) {
return ResponsiveLayout(
child: PopScope(
canPop: false,
onPopInvoked: (didPop) {
if (didPop) return;
if (_selectedIndex != 0) {
setState(() {
_selectedIndex = 0;
});
}
},
child: Scaffold(
backgroundColor: Theme.of(context).scaffoldBackgroundColor,
body: _pages[_selectedIndex],
bottomNavigationBar: BottomNavigationBar(
currentIndex: _selectedIndex,
onTap: _onItemTapped,
type: BottomNavigationBarType.fixed,
selectedItemColor: Colors.blue,
unselectedItemColor: Colors.grey,
selectedLabelStyle: const TextStyle(fontWeight: FontWeight.bold, fontSize: 12),
unselectedLabelStyle: const TextStyle(fontSize: 12),
items: const [
BottomNavigationBarItem(icon: Icon(Icons.home_filled), label: 'Home'),
BottomNavigationBarItem(icon: Icon(Icons.location_searching), label: 'Cari Rute'),
BottomNavigationBarItem(icon: Icon(Icons.history), label: 'Riwayat'),
BottomNavigationBarItem(icon: Icon(Icons.person), label: 'Profil'),
],
),
),
),
);
}
}
class HomeDashboardContent extends StatefulWidget {
final VoidCallback onPlanRoute;
const HomeDashboardContent({super.key, required this.onPlanRoute});
@override
State<HomeDashboardContent> createState() => _HomeDashboardContentState();
}
class _HomeDashboardContentState extends State<HomeDashboardContent> {
bool _isWeekly = true; // Toggle Mingguan/Bulanan
List<dynamic> _history = [];
List<dynamic> _recentRoutes = [];
double _totalDistance = 0;
int _totalSeconds = 0;
bool _isLoading = true;
int _selectedMonth = DateTime.now().month;
int _selectedYear = DateTime.now().year;
int _selectedWeekIndex = 0; // 0: Semua Minggu (Bulanan), 1-4: Minggu spesifik
int? _selectedIndex; // Index titik yang dipilih di grafik
List<double> _chartDataPoints = [];
List<String> _chartLabels = [];
double _maxDistance = 10.0;
@override
void initState() {
super.initState();
_loadDashboardData();
}
String _getMonthName(int month) {
const names = ["Januari", "Februari", "Maret", "April", "Mei", "Juni", "Juli", "Agustus", "September", "Oktober", "November", "Desember"];
return names[month - 1];
}
String _getShortMonthName(int month) {
const names = ["Jan", "Feb", "Mar", "Apr", "Mei", "Jun", "Jul", "Agu", "Sep", "Okt", "Nov", "Des"];
return names[month - 1];
}
List<Map<String, DateTime>> _getWeekRanges(int month, int year) {
List<Map<String, DateTime>> ranges = [];
DateTime date = DateTime(year, month, 1);
int daysToSunday = (7 - date.weekday) % 7;
DateTime currentSunday = date.add(Duration(days: daysToSunday));
while (currentSunday.month == month) {
DateTime saturday = currentSunday.add(const Duration(days: 6));
ranges.add({
'start': currentSunday,
'end': saturday,
});
currentSunday = currentSunday.add(const Duration(days: 7));
}
return ranges;
}
List<String> _generateWeeks(int month, int year) {
List<String> weeks = ["Semua Minggu"];
List<Map<String, DateTime>> ranges = _getWeekRanges(month, year);
for (var range in ranges) {
DateTime start = range['start']!;
DateTime end = range['end']!;
if (start.month == end.month) {
weeks.add("${start.day} - ${end.day} ${_getMonthName(start.month)}");
} else {
weeks.add("${start.day} ${_getMonthName(start.month)} - ${end.day} ${_getMonthName(end.month)}");
}
}
return weeks;
}
Future<void> _loadDashboardData() async {
final url = Uri.parse("${ApiConfig.baseUrl}/get_history.php?id_users=${SessionManager.userId}");
try {
final response = await http.get(url);
if (response.statusCode == 200) {
List<dynamic> decodedHistory = jsonDecode(response.body);
setState(() {
_history = decodedHistory;
_recentRoutes = decodedHistory.take(3).toList();
_calculateStats();
_isLoading = false;
});
}
} catch (e) {
debugPrint("Gagal memuat data riwayat dari server: $e");
setState(() {
_isLoading = false;
});
}
}
void _calculateStats() {
double dist = 0;
int secs = 0;
DateTime startDate;
DateTime endDate;
List<Map<String, DateTime>> ranges = _getWeekRanges(_selectedMonth, _selectedYear);
if (_selectedWeekIndex == 0) {
_isWeekly = false;
if (ranges.isNotEmpty) {
startDate = ranges.first['start']!;
endDate = ranges.last['end']!;
_chartDataPoints = List.filled(ranges.length, 0.0);
_chartLabels = [];
for (var range in ranges) {
DateTime start = range['start']!;
DateTime end = range['end']!;
if (start.month == end.month) {
_chartLabels.add("${start.day}-${end.day} ${_getShortMonthName(start.month)}");
} else {
_chartLabels.add("${start.day} ${_getShortMonthName(start.month)}-${end.day} ${_getShortMonthName(end.month)}");
}
}
} else {
startDate = DateTime(_selectedYear, _selectedMonth, 1);
endDate = DateTime(_selectedYear, _selectedMonth + 1, 0);
_chartDataPoints = [];
_chartLabels = [];
}
} else {
_isWeekly = true;
int index = _selectedWeekIndex - 1;
if (index >= 0 && index < ranges.length) {
startDate = ranges[index]['start']!;
endDate = ranges[index]['end']!;
int daysCount = endDate.difference(startDate).inDays + 1;
_chartDataPoints = List.filled(daysCount, 0.0);
const dayNames = ["Min", "Sen", "Sel", "Rab", "Kam", "Jum", "Sab"];
_chartLabels = List.generate(daysCount, (i) {
DateTime d = startDate.add(Duration(days: i));
return "${dayNames[d.weekday % 7]}, ${d.day}";
});
} else {
startDate = DateTime(_selectedYear, _selectedMonth, 1);
endDate = DateTime(_selectedYear, _selectedMonth + 1, 0);
_chartDataPoints = [];
_chartLabels = [];
}
}
for (var trip in _history) {
DateTime tripDate = DateTime.parse(trip['tanggal']);
if (tripDate.isAfter(startDate.subtract(const Duration(seconds: 1))) &&
tripDate.isBefore(endDate.add(const Duration(days: 1)))) {
double tripDist = double.tryParse(trip['jarak_tempuh']?.toString() ?? "0") ?? 0;
int tripSecs = int.tryParse(trip['waktu_tempuh']?.toString() ?? "0") ?? 0;
int dataIndex = -1;
if (_isWeekly) {
dataIndex = tripDate.difference(startDate).inDays;
} else {
for (int i = 0; i < ranges.length; i++) {
if (tripDate.isAfter(ranges[i]['start']!.subtract(const Duration(seconds: 1))) &&
tripDate.isBefore(ranges[i]['end']!.add(const Duration(days: 1)))) {
dataIndex = i;
break;
}
}
}
if (dataIndex >= 0 && dataIndex < _chartDataPoints.length) {
_chartDataPoints[dataIndex] += tripDist;
if (_selectedIndex == null || _selectedIndex == dataIndex) {
dist += tripDist;
secs += tripSecs;
}
}
}
}
double maxVal = _chartDataPoints.isNotEmpty ? _chartDataPoints.reduce((a, b) => a > b ? a : b) : 0;
_maxDistance = maxVal > 0 ? maxVal : 10.0;
setState(() {
_totalDistance = dist;
_totalSeconds = secs;
});
}
String _formatDuration(int seconds) {
if (seconds <= 0) return "0 menit";
int h = seconds ~/ 3600;
int m = (seconds % 3600) ~/ 60;
if (h > 0) return "$h jam $m menit";
return "$m menit";
}
String getStatSubtitle(bool isDistance) {
if (_selectedIndex != null) {
if (_isWeekly) {
return isDistance
? "Total jarak yang Anda tempuh pada hari ini"
: "Total durasi yang Anda tempuh pada hari ini";
} else {
return isDistance
? "Total jarak yang Anda tempuh pada minggu ini"
: "Total durasi yang Anda tempuh pada minggu ini";
}
} else {
if (_isWeekly) {
return isDistance
? "Total jarak yang Anda tempuh pada minggu ini"
: "Total durasi yang Anda tempuh pada minggu ini";
} else {
return isDistance
? "Total jarak yang Anda tempuh pada bulan ini"
: "Total durasi yang Anda tempuh pada bulan ini";
}
}
}
@override
Widget build(BuildContext context) {
final isDark = Theme.of(context).brightness == Brightness.dark;
return Container(
decoration: BoxDecoration(
gradient: LinearGradient(
begin: Alignment.topLeft,
end: Alignment.bottomRight,
colors: isDark
? [const Color(0xFF121212), const Color(0xFF1E1E1E), const Color(0xFF121212)]
: [const Color(0xFFF8FAFC), const Color(0xFFF1F5F9), const Color(0xFFE2E8F0)],
),
),
child: SafeArea(
child: _isLoading
? const Center(child: CircularProgressIndicator())
: SingleChildScrollView(
physics: const BouncingScrollPhysics(),
child: Padding(
padding: const EdgeInsets.all(24),
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: [
Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Text('Selamat ${DateTime.now().hour < 12 ? "Pagi" : DateTime.now().hour < 15 ? "Siang" : DateTime.now().hour < 18 ? "Sore" : "Malam"},',
style: TextStyle(fontSize: 14, color: isDark ? Colors.blueGrey.shade200 : Colors.blueGrey, fontWeight: FontWeight.w500)),
const SizedBox(height: 4),
Text(
SessionManager.userName ?? "User",
style: TextStyle(
fontSize: 28,
fontWeight: FontWeight.w900,
color: isDark ? Colors.white : const Color(0xFF1E293B),
letterSpacing: -0.5
),
),
],
),
],
),
const SizedBox(height: 24),
Text('Tren Aktivitas Anda', style: TextStyle(fontSize: 20, fontWeight: FontWeight.bold, color: isDark ? Colors.white : const Color(0xFF1E293B), letterSpacing: -0.5)),
const SizedBox(height: 16),
Row(
children: [
Expanded(
child: Container(
padding: const EdgeInsets.symmetric(horizontal: 12),
decoration: BoxDecoration(
color: isDark ? const Color(0xFF252836) : Colors.white,
borderRadius: BorderRadius.circular(12),
border: Border.all(color: isDark ? Colors.white.withOpacity(0.05) : Colors.grey.shade200),
),
child: DropdownButtonHideUnderline(
child: DropdownButton<int>(
value: _selectedMonth,
isExpanded: true,
items: List.generate(12, (i) => DropdownMenuItem(
value: i + 1,
child: FittedBox(
fit: BoxFit.scaleDown,
alignment: Alignment.centerLeft,
child: Text(_getMonthName(i + 1)),
),
)),
onChanged: (val) {
setState(() {
_selectedMonth = val!;
_selectedWeekIndex = 0;
_selectedIndex = null;
_calculateStats();
});
},
),
),
),
),
const SizedBox(width: 16),
Expanded(
child: Container(
padding: const EdgeInsets.symmetric(horizontal: 12),
decoration: BoxDecoration(
color: isDark ? const Color(0xFF252836) : Colors.white,
borderRadius: BorderRadius.circular(12),
border: Border.all(color: isDark ? Colors.white.withOpacity(0.05) : Colors.grey.shade200),
),
child: DropdownButtonHideUnderline(
child: DropdownButton<int>(
value: _selectedWeekIndex,
isExpanded: true,
items: List.generate(_generateWeeks(_selectedMonth, _selectedYear).length, (i) => DropdownMenuItem(
value: i,
child: FittedBox(
fit: BoxFit.scaleDown,
alignment: Alignment.centerLeft,
child: Text(_generateWeeks(_selectedMonth, _selectedYear)[i]),
),
)),
onChanged: (val) {
setState(() {
_selectedWeekIndex = val!;
_selectedIndex = null;
_calculateStats();
});
},
),
),
),
),
],
),
const SizedBox(height: 20),
_buildActivityChart(),
const SizedBox(height: 24),
// --- KARTU STATISTIK ---
Row(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
_buildStatCard(
'Jarak',
'${_totalDistance.toStringAsFixed(1)} km',
Icons.directions_bike,
Colors.blue,
getStatSubtitle(true)
),
const SizedBox(width: 16),
_buildStatCard(
'Waktu',
_formatDuration(_totalSeconds),
Icons.timer,
Colors.orange,
getStatSubtitle(false)
),
],
),
const SizedBox(height: 32),
// --- KARTU VISUAL GRADIENT ---
_buildPlanRouteCard(context),
const SizedBox(height: 32),
Text('Rute Terakhir', style: TextStyle(fontSize: 20, fontWeight: FontWeight.bold, color: isDark ? Colors.white : const Color(0xFF1E293B), letterSpacing: -0.5)),
const SizedBox(height: 16),
_recentRoutes.isEmpty
? const Text("Belum ada rute yang diselesaikan", style: TextStyle(color: Colors.grey))
: Column(
children: _recentRoutes.map((route) => _buildRecentRouteItem(route)).toList(),
),
],
),
),
),
),
);
}
Widget _buildFilterChip(String label, bool isActive, VoidCallback onTap) {
final isDark = Theme.of(context).brightness == Brightness.dark;
return GestureDetector(
onTap: onTap,
child: Container(
padding: const EdgeInsets.symmetric(horizontal: 16, vertical: 8),
decoration: BoxDecoration(
color: isActive ? Colors.blueAccent : (isDark ? const Color(0xFF1E1E1E) : Colors.white),
borderRadius: BorderRadius.circular(12),
border: Border.all(color: isActive ? Colors.blueAccent : (isDark ? Colors.white.withOpacity(0.05) : Colors.grey.shade200)),
boxShadow: isActive ? [BoxShadow(color: Colors.blue.withOpacity(isDark ? 0.1 : 0.2), blurRadius: 8)] : null,
),
child: Center(
child: Text(
label,
style: TextStyle(
color: isActive ? Colors.white : Colors.grey.shade600,
fontWeight: FontWeight.bold,
fontSize: 12,
),
),
),
),
);
}
Widget _buildActivityChart() {
final isDark = Theme.of(context).brightness == Brightness.dark;
return Container(
height: 240,
width: double.infinity,
padding: const EdgeInsets.fromLTRB(15, 25, 20, 10),
decoration: BoxDecoration(
color: isDark ? const Color(0xFF1E1E1E) : Colors.white,
borderRadius: BorderRadius.circular(28),
boxShadow: [
BoxShadow(
color: Colors.black.withOpacity(isDark ? 0.3 : 0.04),
blurRadius: 24,
offset: const Offset(0, 12),
),
],
border: isDark ? Border.all(color: Colors.white.withOpacity(0.05), width: 1) : null,
),
child: _chartDataPoints.isEmpty
? const Center(child: Text("Tidak ada data", style: TextStyle(color: Colors.grey)))
: LineChart(
LineChartData(
gridData: FlGridData(
show: true,
drawVerticalLine: true,
horizontalInterval: 0.2,
verticalInterval: 1,
getDrawingHorizontalLine: (value) => FlLine(color: isDark ? Colors.white.withOpacity(0.05) : Colors.grey.shade100, strokeWidth: 1),
getDrawingVerticalLine: (value) => FlLine(color: isDark ? Colors.white.withOpacity(0.05) : Colors.grey.shade100, strokeWidth: 1),
),
titlesData: FlTitlesData(
show: true,
rightTitles: const AxisTitles(sideTitles: SideTitles(showTitles: false)),
topTitles: const AxisTitles(sideTitles: SideTitles(showTitles: false)),
bottomTitles: AxisTitles(
sideTitles: SideTitles(
showTitles: true,
reservedSize: 30,
interval: 1,
getTitlesWidget: (value, meta) {
int index = value.toInt();
if (index >= 0 && index < _chartLabels.length) {
return SideTitleWidget(
axisSide: meta.axisSide,
child: Text(
_chartLabels[index],
style: TextStyle(
color: _selectedIndex == index ? Colors.orangeAccent : (isDark ? Colors.white54 : Colors.grey),
fontSize: 10,
fontWeight: _selectedIndex == index ? FontWeight.bold : FontWeight.normal,
),
),
);
}
return const SizedBox();
},
),
),
leftTitles: AxisTitles(
sideTitles: SideTitles(
showTitles: true,
reservedSize: 45,
interval: _maxDistance > 0 ? (_maxDistance / 5).ceilToDouble().clamp(1.0, 100.0) : 2.0,
getTitlesWidget: (value, meta) {
return SideTitleWidget(
axisSide: meta.axisSide,
child: Text(
"${value.toStringAsFixed(0)} km",
style: TextStyle(color: isDark ? Colors.white54 : Colors.grey, fontSize: 10),
),
);
},
),
),
),
borderData: FlBorderData(
show: false,
),
minX: 0,
maxX: (_chartDataPoints.length - 1).toDouble(),
minY: 0,
maxY: (_maxDistance * 1.25).ceilToDouble(),
lineBarsData: [
LineChartBarData(
spots: List.generate(_chartDataPoints.length, (i) => FlSpot(i.toDouble(), _chartDataPoints[i])),
isCurved: true,
color: Colors.blueAccent,
barWidth: 3,
isStrokeCapRound: true,
dotData: FlDotData(
show: true,
getDotPainter: (spot, percent, barData, index) {
bool isSelected = _selectedIndex == index;
return FlDotCirclePainter(
radius: isSelected ? 6 : 4,
color: isSelected ? Colors.orangeAccent : Colors.blue,
strokeWidth: 2,
strokeColor: Colors.white,
);
},
),
belowBarData: BarAreaData(
show: true,
color: Colors.blueAccent.withOpacity(0.05),
),
),
],
lineTouchData: LineTouchData(
touchCallback: (FlTouchEvent event, LineTouchResponse? touchResponse) {
if (!event.isInterestedForInteractions || touchResponse == null || touchResponse.lineBarSpots == null) {
return;
}
if (event is FlTapDownEvent || event is FlTapUpEvent) {
final spotIndex = touchResponse.lineBarSpots!.first.spotIndex;
setState(() {
if (_selectedIndex == spotIndex) {
_selectedIndex = null;
} else {
_selectedIndex = spotIndex;
}
_calculateStats();
});
}
},
handleBuiltInTouches: true,
),
),
),
);
}
Widget _buildStatCard(String title, String value, IconData icon, Color color, String subtitle) {
final isDark = Theme.of(context).brightness == Brightness.dark;
return Expanded(
child: Container(
clipBehavior: Clip.antiAlias,
decoration: BoxDecoration(
color: isDark ? const Color(0xFF252836) : Colors.white,
borderRadius: BorderRadius.circular(24),
boxShadow: [
BoxShadow(
color: isDark ? Colors.black.withOpacity(0.4) : color.withOpacity(0.1),
blurRadius: 15,
offset: const Offset(0, 8),
),
],
border: Border.all(
color: isDark ? Colors.white.withOpacity(0.05) : Colors.grey.shade200,
width: 1,
),
),
child: Stack(
children: [
// Thick left accent border
Positioned(
left: 0,
top: 0,
bottom: 0,
child: Container(width: 6, color: color),
),
Padding(
padding: const EdgeInsets.all(16),
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
mainAxisSize: MainAxisSize.min,
children: [
Container(
padding: const EdgeInsets.all(10),
decoration: BoxDecoration(
color: color.withOpacity(0.15),
borderRadius: BorderRadius.circular(12),
),
child: Icon(icon, color: color, size: 22),
),
const SizedBox(height: 16),
Text(
value,
style: TextStyle(
fontSize: 24,
fontWeight: FontWeight.w900,
color: isDark ? Colors.white : const Color(0xFF1E293B),
letterSpacing: -0.5
)
),
const SizedBox(height: 4),
Text(
title,
style: TextStyle(
color: isDark ? Colors.white.withOpacity(0.9) : Colors.blueGrey.shade700,
fontSize: 14,
fontWeight: FontWeight.w700
)
),
const SizedBox(height: 6),
Text(
subtitle,
style: TextStyle(
color: isDark ? Colors.white54 : Colors.grey.shade500,
fontSize: 11,
fontStyle: FontStyle.italic,
height: 1.2
)
),
],
),
),
],
),
),
);
}
Widget _buildPlanRouteCard(BuildContext context) {
return Container(
width: double.infinity,
padding: const EdgeInsets.all(24),
decoration: BoxDecoration(
gradient: const LinearGradient(
colors: [Color(0xFF3B82F6), Color(0xFF2563EB)],
begin: Alignment.topLeft,
end: Alignment.bottomRight,
),
borderRadius: BorderRadius.circular(28),
boxShadow: [
BoxShadow(
color: Colors.blue.withOpacity(0.3),
blurRadius: 25,
offset: const Offset(0, 12),
)
],
),
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
const Text('Siap untuk gowes hari ini?',
style: TextStyle(color: Colors.white, fontSize: 20, fontWeight: FontWeight.w900, letterSpacing: -0.5)),
const SizedBox(height: 8),
const Text('Temukan rute terbaik yang aman dan nyaman untukmu.',
style: TextStyle(color: Colors.white70, fontSize: 14, fontWeight: FontWeight.w500)),
const SizedBox(height: 24),
ElevatedButton(
onPressed: widget.onPlanRoute,
style: ElevatedButton.styleFrom(
backgroundColor: Colors.white,
foregroundColor: Colors.blue.shade700,
elevation: 0,
shape: RoundedRectangleBorder(borderRadius: BorderRadius.circular(14)),
padding: const EdgeInsets.symmetric(horizontal: 24, vertical: 12),
),
child: const Row(
mainAxisSize: MainAxisSize.min,
children: [
Text('Mulai Jelajah', style: TextStyle(fontWeight: FontWeight.w800)),
SizedBox(width: 8),
Icon(Icons.arrow_forward_rounded, size: 16),
],
),
),
],
),
);
}
Widget _buildNotificationBadge(BuildContext context) {
return Stack(
children: [
IconButton(
onPressed: () => ScaffoldMessenger.of(context).showSnackBar(const SnackBar(content: Text("Belum ada notifikasi baru"))),
icon: const Icon(Icons.notifications_active, color: Colors.blueAccent, size: 30),
),
Positioned(
right: 8, top: 8,
child: Container(
padding: const EdgeInsets.all(4),
decoration: const BoxDecoration(color: Colors.red, shape: BoxShape.circle),
constraints: const BoxConstraints(minWidth: 8, minHeight: 8),
),
)
],
);
}
Widget _buildRecentRouteItem(dynamic route) {
final isDark = Theme.of(context).brightness == Brightness.dark;
String name = route['nama_rute'] ?? '-';
String distance = '${route['jarak_tempuh'] ?? route['jarak'] ?? 0} km';
String statusText = route['status']?.toString().toUpperCase() ?? 'GAGAL';
if (statusText == "0") statusText = "GAGAL";
return GestureDetector(
onTap: () {
Navigator.push(
context,
MaterialPageRoute(
builder: (context) => HistoryDetailScreen(historyItem: route),
),
);
},
child: Container(
margin: const EdgeInsets.only(bottom: 16),
padding: const EdgeInsets.all(18),
decoration: BoxDecoration(
color: isDark ? const Color(0xFF1E1E1E) : Colors.white,
borderRadius: BorderRadius.circular(24),
boxShadow: [
BoxShadow(
color: Colors.black.withOpacity(isDark ? 0.2 : 0.03),
blurRadius: 20,
offset: const Offset(0, 8),
),
],
border: isDark ? Border.all(color: Colors.white.withOpacity(0.05), width: 1) : null,
),
child: Row(
children: [
Container(
padding: const EdgeInsets.all(12),
decoration: BoxDecoration(
gradient: LinearGradient(
colors: [Colors.blue.shade400, Colors.blue.shade700],
begin: Alignment.topLeft,
end: Alignment.bottomRight,
),
borderRadius: BorderRadius.circular(16),
boxShadow: [
BoxShadow(color: Colors.blue.withOpacity(isDark ? 0.1 : 0.3), blurRadius: 8, offset: const Offset(0, 4))
],
),
child: const Icon(Icons.directions_bike_rounded, color: Colors.white, size: 24),
),
const SizedBox(width: 18),
Expanded(
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Text(
name,
style: TextStyle(
fontWeight: FontWeight.w800,
fontSize: 15,
color: isDark ? Colors.white : const Color(0xFF1E293B)
),
overflow: TextOverflow.ellipsis
),
const SizedBox(height: 6),
Row(
children: [
Icon(Icons.straighten, size: 14, color: isDark ? Colors.blueGrey.shade400 : Colors.blueGrey.shade300),
const SizedBox(width: 4),
Text(
distance,
style: TextStyle(
color: isDark ? Colors.blueGrey.shade300 : Colors.blueGrey.shade400,
fontSize: 13,
fontWeight: FontWeight.w500
)
),
const SizedBox(width: 12),
Container(
padding: const EdgeInsets.symmetric(horizontal: 8, vertical: 2),
decoration: BoxDecoration(
color: statusText == "BERHASIL" ? Colors.green.withOpacity(0.1) : Colors.red.withOpacity(0.1),
borderRadius: BorderRadius.circular(8),
),
child: Text(
statusText,
style: TextStyle(
color: statusText == "BERHASIL" ? Colors.greenAccent : Colors.redAccent,
fontSize: 10,
fontWeight: FontWeight.w900
)
),
),
],
),
],
),
),
Icon(Icons.arrow_forward_ios_rounded, size: 16, color: isDark ? Colors.white24 : Colors.blueGrey.shade200),
],
),
),
);
}
}