import 'dart:async'; import 'dart:convert'; import 'dart:math' as math; import 'package:flutter/material.dart'; import 'package:flutter_map/flutter_map.dart'; import 'package:latlong2/latlong.dart'; import 'package:http/http.dart' as http; import 'package:siro_admin/constant/links.dart'; import 'package:url_launcher/url_launcher.dart'; import '../../../constant/box_name.dart'; import '../../../controller/functions/crud.dart'; import '../../../main.dart'; class SiroTrackerScreen extends StatefulWidget { const SiroTrackerScreen({super.key}); @override State createState() => _SiroTrackerScreenState(); } class _SiroTrackerScreenState extends State with TickerProviderStateMixin { // === Map Controller === final MapController _mapController = MapController(); List _markers = []; // === State Variables === bool isLiveMode = true; bool isLoading = false; String lastUpdated = "جاري التحميل..."; // === Counters === int liveCount = 0; int dayCount = 0; Timer? _timer; // === Animation Controllers === late AnimationController _fadeController; late AnimationController _scaleController; // === Admin Info === String myPhone = box.read(BoxName.adminPhone).toString(); bool get isSuperAdmin => myPhone == '963942542053' || myPhone == '963992952235'; // === URLs === final String _baseDir = "${AppLink.server}/ride/location/"; @override void initState() { super.initState(); _initAnimations(); fetchData(); _timer = Timer.periodic(const Duration(minutes: 5), (timer) { if (mounted) fetchData(); }); } void _initAnimations() { _fadeController = AnimationController( duration: const Duration(milliseconds: 800), vsync: this, ); _scaleController = AnimationController( duration: const Duration(milliseconds: 600), vsync: this, ); _fadeController.forward(); _scaleController.forward(); } @override void dispose() { _timer?.cancel(); _mapController.dispose(); _fadeController.dispose(); _scaleController.dispose(); super.dispose(); } Future _makePhoneCall(String phoneNumber) async { final Uri launchUri = Uri(scheme: 'tel', path: phoneNumber); if (await canLaunchUrl(launchUri)) { await launchUrl(launchUri); } else { _showSnackBar("لا يمكن إجراء الاتصال لهذا الرقم"); } } void _showSnackBar(String message) { ScaffoldMessenger.of(context).showSnackBar( SnackBar( content: Text(message), backgroundColor: const Color(0xFF2C3E50), behavior: SnackBarBehavior.floating, margin: const EdgeInsets.all(16), shape: RoundedRectangleBorder(borderRadius: BorderRadius.circular(12)), ), ); } Future fetchData() async { if (!mounted) return; setState(() => isLoading = true); try { String updateUrl = "${_baseDir}getUpdatedLocationForAdmin.php?mode=${isLiveMode ? 'live' : 'day'}"; print("📡 Calling Update URL: $updateUrl"); var responseUpdate = await CRUD().post(link: updateUrl, payload: {}); print("📡 Update Response: $responseUpdate"); String v = DateTime.now().millisecondsSinceEpoch.toString(); String liveUrl = "${_baseDir}locations_live.json?v=$v"; print("📡 Calling Live JSON URL: $liveUrl"); final responseLive = await http.get(Uri.parse(liveUrl)); print( "📡 Live JSON Response (${responseLive.statusCode}): ${responseLive.body.length > 100 ? responseLive.body.substring(0, 100) : responseLive.body}"); if (responseLive.statusCode == 200) { final data = json.decode(responseLive.body); List drivers = (data is Map && data.containsKey('drivers')) ? data['drivers'] : data; setState(() { liveCount = drivers.length; if (isLiveMode) _buildMarkers(drivers); }); } String dayUrl = "${_baseDir}locations_day.json?v=$v"; print("📡 Calling Day JSON URL: $dayUrl"); final responseDay = await http.get(Uri.parse(dayUrl)); print( "📡 Day JSON Response (${responseDay.statusCode}): ${responseDay.body.length > 100 ? responseDay.body.substring(0, 100) : responseDay.body}"); if (responseDay.statusCode == 200) { final data = json.decode(responseDay.body); List drivers = (data is Map && data.containsKey('drivers')) ? data['drivers'] : data; setState(() { dayCount = drivers.length; if (!isLiveMode) _buildMarkers(drivers); }); } setState(() { lastUpdated = DateTime.now().toString().substring(11, 19); }); } catch (e) { print("Exception: $e"); setState(() => lastUpdated = "خطأ في الاتصال"); } finally { if (mounted) setState(() => isLoading = false); } } void _buildMarkers(List drivers) { List newMarkers = []; for (var d in drivers) { double lat = double.tryParse((d['lat'] ?? "0").toString()) ?? 0.0; double lon = double.tryParse((d['lon'] ?? "0").toString()) ?? 0.0; double heading = double.tryParse((d['heading'] ?? "0").toString()) ?? 0.0; String id = (d['id'] ?? "Unknown").toString(); String speed = (d['speed'] ?? "0").toString(); String name = (d['name'] ?? "كابتن").toString(); String phone = (d['phone'] ?? "").toString(); String completed = (d['completed'] ?? "0").toString(); String cancelled = (d['cancelled'] ?? "0").toString(); if (lat != 0 && lon != 0) { newMarkers.add( Marker( point: LatLng(lat, lon), width: 60, height: 60, child: GestureDetector( onTap: () { _showDriverInfoDialog( driverId: id, name: name, phone: phone, speed: speed, heading: heading, completed: completed, cancelled: cancelled, ); }, child: _buildMarkerWidget(heading), ), ), ); } } setState(() { _markers = newMarkers; }); } Widget _buildMarkerWidget(double heading) { return Stack( alignment: Alignment.center, children: [ Container( width: 44, height: 44, decoration: BoxDecoration( shape: BoxShape.circle, gradient: LinearGradient( colors: isLiveMode ? [const Color(0xFF27AE60), const Color(0xFF229954)] : [const Color(0xFF3498DB), const Color(0xFF2980B9)], begin: Alignment.topLeft, end: Alignment.bottomRight, ), boxShadow: [ BoxShadow( color: (isLiveMode ? const Color(0xFF27AE60) : const Color(0xFF3498DB)) .withOpacity(0.5), blurRadius: 12, spreadRadius: 2, ) ], ), ), Transform.rotate( angle: heading * (math.pi / 180), child: Icon( Icons.navigation, color: Colors.white, size: 26, ), ), ], ); } void _showDriverInfoDialog({ required String driverId, required String name, required String phone, required String speed, required double heading, required String completed, required String cancelled, }) { showDialog( context: context, builder: (_) => Dialog( shape: RoundedRectangleBorder(borderRadius: BorderRadius.circular(24)), backgroundColor: Colors.transparent, child: Container( decoration: BoxDecoration( gradient: LinearGradient( colors: [Colors.white, Colors.grey.shade50], begin: Alignment.topLeft, end: Alignment.bottomRight, ), borderRadius: BorderRadius.circular(24), boxShadow: [ BoxShadow( color: Colors.black.withOpacity(0.15), blurRadius: 30, spreadRadius: 5, ) ], ), child: Padding( padding: const EdgeInsets.all(24), child: Column( mainAxisSize: MainAxisSize.min, children: [ Container( padding: const EdgeInsets.symmetric(horizontal: 16, vertical: 12), decoration: BoxDecoration( gradient: LinearGradient( colors: isLiveMode ? [const Color(0xFF27AE60), const Color(0xFF229954)] : [const Color(0xFF3498DB), const Color(0xFF2980B9)], ), borderRadius: BorderRadius.circular(12), ), child: Row( mainAxisSize: MainAxisSize.min, children: [ Icon(Icons.person_outline, color: Colors.white, size: 20), const SizedBox(width: 8), const Text( "معلومات الكابتن", style: TextStyle( fontSize: 18, fontWeight: FontWeight.bold, color: Colors.white, ), ), ], ), ), const SizedBox(height: 20), _buildInfoCard(Icons.person, "الاسم", name), const SizedBox(height: 12), _buildInfoCard(Icons.badge, "المعرف", driverId), const SizedBox(height: 12), _buildInfoCard(Icons.speed, "السرعة", "$speed كم/س"), const SizedBox(height: 20), _buildStatsContainer(completed, cancelled), if (isSuperAdmin) ...[ const SizedBox(height: 16), _buildPhoneButton(phone), ], const SizedBox(height: 20), SizedBox( width: double.infinity, child: ElevatedButton( onPressed: () => Navigator.pop(context), style: ElevatedButton.styleFrom( backgroundColor: const Color(0xFF2C3E50), foregroundColor: Colors.white, padding: const EdgeInsets.symmetric(vertical: 14), shape: RoundedRectangleBorder( borderRadius: BorderRadius.circular(12), ), elevation: 0, ), child: const Text( "إغلاق", style: TextStyle( fontSize: 16, fontWeight: FontWeight.bold, ), ), ), ) ], ), ), ), ), ); } Widget _buildInfoCard(IconData icon, String label, String value) { return Container( padding: const EdgeInsets.all(14), decoration: BoxDecoration( color: Colors.white, borderRadius: BorderRadius.circular(12), border: Border.all( color: Colors.grey.shade200, width: 1, ), boxShadow: [ BoxShadow( color: Colors.black.withOpacity(0.05), blurRadius: 8, ) ], ), child: Row( children: [ Container( padding: const EdgeInsets.all(8), decoration: BoxDecoration( color: Colors.grey.shade100, borderRadius: BorderRadius.circular(8), ), child: Icon(icon, color: const Color(0xFF2C3E50), size: 20), ), const SizedBox(width: 12), Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ Text( label, style: TextStyle( fontSize: 12, color: Colors.grey.shade600, fontWeight: FontWeight.w500, ), ), const SizedBox(height: 4), Text( value, style: const TextStyle( fontSize: 15, fontWeight: FontWeight.bold, color: Color(0xFF2C3E50), ), ), ], ), ], ), ); } Widget _buildStatsContainer(String completed, String cancelled) { return Container( padding: const EdgeInsets.all(16), decoration: BoxDecoration( gradient: LinearGradient( colors: [Colors.grey.shade50, Colors.grey.shade100], begin: Alignment.topLeft, end: Alignment.bottomRight, ), borderRadius: BorderRadius.circular(12), border: Border.all(color: Colors.grey.shade200), ), child: Row( mainAxisAlignment: MainAxisAlignment.spaceEvenly, children: [ _buildStatItem("✓ مكتملة", completed, const Color(0xFF27AE60)), Container( width: 1, height: 40, color: Colors.grey.shade300, ), _buildStatItem("✕ ملغاة", cancelled, const Color(0xFFE74C3C)), ], ), ); } Widget _buildStatItem(String label, String value, Color color) { return Column( children: [ Text( value, style: TextStyle( color: color, fontWeight: FontWeight.bold, fontSize: 20, ), ), const SizedBox(height: 4), Text( label, style: TextStyle( fontSize: 12, color: Colors.grey.shade600, fontWeight: FontWeight.w500, ), ), ], ); } Widget _buildPhoneButton(String phone) { return InkWell( onTap: () { if (phone.isNotEmpty) _makePhoneCall(phone); }, borderRadius: BorderRadius.circular(12), child: Container( width: double.infinity, padding: const EdgeInsets.symmetric(vertical: 12, horizontal: 14), decoration: BoxDecoration( gradient: LinearGradient( colors: [const Color(0xFFFFA500), const Color(0xFFFF8C00)], begin: Alignment.topLeft, end: Alignment.bottomRight, ), borderRadius: BorderRadius.circular(12), boxShadow: [ BoxShadow( color: const Color(0xFFFFA500).withOpacity(0.4), blurRadius: 12, spreadRadius: 2, ) ], ), child: Row( mainAxisAlignment: MainAxisAlignment.center, children: [ const Icon(Icons.call, color: Colors.white, size: 20), const SizedBox(width: 10), Expanded( child: Text( phone, style: const TextStyle( color: Colors.white, fontWeight: FontWeight.bold, fontSize: 15, ), textAlign: TextAlign.center, ), ), ], ), ), ); } @override Widget build(BuildContext context) { return Scaffold( extendBodyBehindAppBar: true, appBar: AppBar( elevation: 0, backgroundColor: Colors.transparent, centerTitle: true, title: Container( padding: const EdgeInsets.symmetric(horizontal: 16, vertical: 8), decoration: BoxDecoration( color: const Color(0xFF2C3E50).withOpacity(0.9), borderRadius: BorderRadius.circular(12), // backdropFilter: const BackdropFilter(blur: 10), ), child: const Text( "نظام تتبع الكابتن", style: TextStyle( fontSize: 18, fontWeight: FontWeight.bold, color: Colors.white, ), ), ), foregroundColor: Colors.white, ), body: Stack( children: [ FlutterMap( mapController: _mapController, options: const MapOptions( initialCenter: LatLng(33.513, 36.276), initialZoom: 10.0, ), children: [ TileLayer( urlTemplate: 'https://tile.openstreetmap.org/{z}/{x}/{y}.png', userAgentPackageName: 'com.tripz.app', ), MarkerLayer(markers: _markers), ], ), _buildDashboard(), ], ), ); } Widget _buildDashboard() { return Positioned( top: 100, right: 16, child: FadeTransition( opacity: _fadeController, child: ScaleTransition( scale: _scaleController, child: Container( width: 300, decoration: BoxDecoration( color: Colors.white, borderRadius: BorderRadius.circular(20), boxShadow: [ BoxShadow( color: Colors.black.withOpacity(0.15), blurRadius: 30, spreadRadius: 5, ) ], ), child: ClipRRect( borderRadius: BorderRadius.circular(20), child: Column( children: [ // Header Container( padding: const EdgeInsets.all(16), decoration: BoxDecoration( gradient: LinearGradient( colors: [ const Color(0xFF2C3E50), const Color(0xFF34495E) ], begin: Alignment.topLeft, end: Alignment.bottomRight, ), ), child: Row( mainAxisAlignment: MainAxisAlignment.spaceBetween, children: [ const Icon(Icons.dashboard, color: Colors.white, size: 22), const Text( "لوحة التحكم", style: TextStyle( color: Colors.white, fontSize: 16, fontWeight: FontWeight.bold, ), ), ], ), ), Padding( padding: const EdgeInsets.all(16), child: Column( crossAxisAlignment: CrossAxisAlignment.end, children: [ // Mode Buttons Row( children: [ Expanded( child: _buildModeButton( "أرشيف اليوم", !isLiveMode, () { setState(() => isLiveMode = false); fetchData(); }, const Color(0xFF3498DB), ), ), const SizedBox(width: 10), Expanded( child: _buildModeButton( "مباشر", isLiveMode, () { setState(() => isLiveMode = true); fetchData(); }, const Color(0xFF27AE60), ), ), ], ), const SizedBox(height: 16), // Stats _buildStatRow( icon: Icons.live_tv, label: "نشط الآن (مباشر)", value: liveCount.toString(), color: const Color(0xFF27AE60), ), const SizedBox(height: 12), _buildStatRow( icon: Icons.history, label: "إجمالي اليوم", value: dayCount.toString(), color: const Color(0xFF3498DB), ), const SizedBox(height: 14), // Last Update Container( padding: const EdgeInsets.all(10), decoration: BoxDecoration( color: Colors.grey.shade50, borderRadius: BorderRadius.circular(8), border: Border.all(color: Colors.grey.shade200), ), child: Row( mainAxisAlignment: MainAxisAlignment.spaceBetween, children: [ Text( isLoading ? "جاري التحديث..." : "تحديث: $lastUpdated", style: TextStyle( fontSize: 11, color: Colors.grey.shade600, fontWeight: FontWeight.w500, ), ), Icon( isLoading ? Icons.hourglass_bottom : Icons.check_circle, size: 14, color: isLoading ? Colors.orange : Colors.green, ), ], ), ), const SizedBox(height: 12), // Refresh Button SizedBox( width: double.infinity, child: ElevatedButton( onPressed: isLoading ? null : fetchData, style: ElevatedButton.styleFrom( backgroundColor: const Color(0xFF2C3E50), foregroundColor: Colors.white, disabledBackgroundColor: Colors.grey.shade300, padding: const EdgeInsets.symmetric(vertical: 12), shape: RoundedRectangleBorder( borderRadius: BorderRadius.circular(10), ), elevation: 0, ), child: Row( mainAxisAlignment: MainAxisAlignment.center, children: [ if (isLoading) const SizedBox( width: 16, height: 16, child: CircularProgressIndicator( strokeWidth: 2, valueColor: AlwaysStoppedAnimation( Colors.white, ), ), ) else const Icon(Icons.refresh, size: 18), const SizedBox(width: 8), const Text( "تحديث البيانات", style: TextStyle( fontWeight: FontWeight.bold, fontSize: 14, ), ), ], ), ), ), ], ), ), ], ), ), ), ), ), ); } Widget _buildModeButton( String title, bool active, VoidCallback onTap, Color color, ) { return InkWell( onTap: onTap, borderRadius: BorderRadius.circular(10), child: AnimatedContainer( duration: const Duration(milliseconds: 300), padding: const EdgeInsets.symmetric(vertical: 10), alignment: Alignment.center, decoration: BoxDecoration( gradient: active ? LinearGradient( colors: [color, color.withOpacity(0.8)], begin: Alignment.topLeft, end: Alignment.bottomRight, ) : null, color: active ? null : Colors.grey.shade100, borderRadius: BorderRadius.circular(10), border: Border.all( color: active ? color : Colors.grey.shade300, width: 1.5, ), boxShadow: active ? [ BoxShadow( color: color.withOpacity(0.3), blurRadius: 8, spreadRadius: 1, ) ] : null, ), child: Text( title, style: TextStyle( color: active ? Colors.white : Colors.grey.shade700, fontWeight: active ? FontWeight.bold : FontWeight.w600, fontSize: 13, ), ), ), ); } Widget _buildStatRow({ required IconData icon, required String label, required String value, required Color color, }) { return Row( children: [ Container( padding: const EdgeInsets.all(8), decoration: BoxDecoration( color: color.withOpacity(0.1), borderRadius: BorderRadius.circular(8), ), child: Icon(icon, color: color, size: 18), ), const SizedBox(width: 12), Expanded( child: Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ Text( label, style: TextStyle( fontSize: 12, color: Colors.grey.shade600, fontWeight: FontWeight.w500, ), ), const SizedBox(height: 2), Text( value, style: TextStyle( fontSize: 16, fontWeight: FontWeight.bold, color: color, ), ), ], ), ), ], ); } }