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:url_launcher/url_launcher.dart'; // ضروري من أجل الاتصال import '../../../constant/box_name.dart'; import '../../../main.dart'; class IntaleqTrackerScreen extends StatefulWidget { const IntaleqTrackerScreen({super.key}); @override State createState() => _IntaleqTrackerScreenState(); } class _IntaleqTrackerScreenState extends State { // === 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; // === Admin Info === String myPhone = box.read(BoxName.adminPhone).toString(); bool get isSuperAdmin => myPhone == '963942542053' || myPhone == '963992952235'; // === URLs === final String _baseDir = "https://api.intaleq.xyz/intaleq/ride/location/"; @override void initState() { super.initState(); fetchData(); // === تعديل 1: التحديث كل 5 دقائق بدلاً من 15 ثانية === _timer = Timer.periodic(const Duration(minutes: 5), (timer) { if (mounted) fetchData(); }); } @override void dispose() { _timer?.cancel(); _mapController.dispose(); super.dispose(); } // === دالة إجراء الاتصال === Future _makePhoneCall(String phoneNumber) async { final Uri launchUri = Uri(scheme: 'tel', path: phoneNumber); if (await canLaunchUrl(launchUri)) { await launchUrl(launchUri); } else { ScaffoldMessenger.of(context).showSnackBar( const SnackBar(content: Text("لا يمكن إجراء الاتصال لهذا الرقم")), ); } } // === Fetch Data Function === Future fetchData() async { if (!mounted) return; setState(() => isLoading = true); try { // 1. طلب التحديث من PHP String updateUrl = "${_baseDir}getUpdatedLocationForAdmin.php?mode=${isLiveMode ? 'live' : 'day'}"; await http.get(Uri.parse(updateUrl)); String v = DateTime.now().millisecondsSinceEpoch.toString(); // === Live Data === final responseLive = await http.get(Uri.parse("${_baseDir}locations_live.json?v=$v")); 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); }); } // === Day Data === final responseDay = await http.get(Uri.parse("${_baseDir}locations_day.json?v=$v")); 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); } } // === Build Markers === 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: 50, height: 50, child: GestureDetector( onTap: () { _showDriverInfoDialog( driverId: id, name: name, phone: phone, speed: speed, heading: heading, completed: completed, cancelled: cancelled, ); }, child: Stack( alignment: Alignment.center, children: [ Container( width: 32, height: 32, decoration: BoxDecoration( color: Colors.white.withOpacity(0.9), shape: BoxShape.circle, boxShadow: const [ BoxShadow(blurRadius: 3, color: Colors.black26) ]), ), Transform.rotate( angle: heading * (math.pi / 180), child: Icon( Icons.navigation, color: isLiveMode ? const Color(0xFF27AE60) : const Color(0xFF2980B9), size: 28, ), ), ], ), ), ), ); } } setState(() { _markers = newMarkers; }); } // === Dialog Function === 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(16)), backgroundColor: Colors.white, child: Padding( padding: const EdgeInsets.all(20), child: Column( mainAxisSize: MainAxisSize.min, children: [ const Text("بيانات الكابتن", style: TextStyle( fontSize: 18, fontWeight: FontWeight.bold, color: Color(0xFF2C3E50))), const Divider(thickness: 1, height: 25), _infoRow(Icons.person, "الاسم", name), _infoRow(Icons.badge, "المعرف (ID)", driverId), _infoRow(Icons.speed, "السرعة", "$speed كم/س"), const SizedBox(height: 10), 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.spaceAround, children: [ _statItem("مكتملة", completed, Colors.green), Container( width: 1, height: 30, color: Colors.grey.shade300), _statItem("ملغاة", cancelled, Colors.red), ], ), ), // === تعديل 2: جعل رقم الهاتف قابلاً للنقر === if (isSuperAdmin) ...[ const SizedBox(height: 15), InkWell( onTap: () { if (phone.isNotEmpty) _makePhoneCall(phone); }, borderRadius: BorderRadius.circular(8), child: Container( padding: const EdgeInsets.symmetric(vertical: 8, horizontal: 10), decoration: BoxDecoration( color: const Color(0xFFFFF3CD), borderRadius: BorderRadius.circular(8), border: Border.all(color: const Color(0xFFFFEEBA))), child: Row( children: [ Expanded( child: _infoRow(Icons.phone, "الهاتف", phone, isPrivate: true)), const SizedBox(width: 5), const Icon(Icons.call, color: Colors.green, size: 20), ], ), ), ), ], const SizedBox(height: 20), SizedBox( width: double.infinity, child: ElevatedButton( onPressed: () => Navigator.pop(context), style: ElevatedButton.styleFrom( backgroundColor: const Color(0xFF2C3E50), foregroundColor: Colors.white, ), child: const Text("إغلاق"), ), ) ], ), ), ), ); } // Helper Widgets Widget _infoRow(IconData icon, String label, String value, {bool isPrivate = false}) { return Padding( padding: const EdgeInsets.symmetric(vertical: 4), child: Row( children: [ Icon(icon, size: 20, color: isPrivate ? Colors.orange[800] : Colors.grey[600]), const SizedBox(width: 8), Text("$label: ", style: const TextStyle(fontWeight: FontWeight.bold, fontSize: 13)), Expanded( child: Text(value, style: TextStyle( fontSize: 14, fontWeight: isPrivate ? FontWeight.bold : FontWeight.normal), textAlign: TextAlign.end)), ], ), ); } Widget _statItem(String label, String val, Color color) { return Column( children: [ Text(val, style: TextStyle( color: color, fontWeight: FontWeight.bold, fontSize: 16)), Text(label, style: const TextStyle(fontSize: 11, color: Colors.grey)), ], ); } @override Widget build(BuildContext context) { return Scaffold( appBar: AppBar( title: const Text("نظام تتبع الكباتن"), backgroundColor: const Color(0xFF2C3E50), 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), ], ), // === Dashboard === Positioned( top: 20, right: 15, child: Container( width: 260, padding: const EdgeInsets.all(15), decoration: BoxDecoration( color: Colors.white.withOpacity(0.95), borderRadius: BorderRadius.circular(12), boxShadow: const [ BoxShadow(color: Colors.black12, blurRadius: 10) ]), child: Column( crossAxisAlignment: CrossAxisAlignment.end, children: [ const Text("لوحة التحكم", style: TextStyle(fontWeight: FontWeight.bold)), const Divider(), // أزرار التبديل Row( children: [ Expanded( child: _modeBtn("أرشيف اليوم", !isLiveMode, () { setState(() => isLiveMode = false); fetchData(); })), const SizedBox(width: 8), Expanded( child: _modeBtn("مباشر", isLiveMode, () { setState(() => isLiveMode = true); fetchData(); })), ], ), const SizedBox(height: 15), // === عرض العدادين معاً === Row( mainAxisAlignment: MainAxisAlignment.spaceBetween, children: [ Text("$liveCount", style: const TextStyle( color: Color(0xFF27AE60), fontWeight: FontWeight.bold, fontSize: 14)), const Text("نشط الآن (مباشر):", style: TextStyle(fontSize: 12)), ], ), const SizedBox(height: 5), Row( mainAxisAlignment: MainAxisAlignment.spaceBetween, children: [ Text("$dayCount", style: const TextStyle( color: Color(0xFF2980B9), fontWeight: FontWeight.bold, fontSize: 14)), const Text("إجمالي اليوم:", style: TextStyle(fontSize: 12)), ], ), const SizedBox(height: 10), Text(isLoading ? "جاري التحديث..." : "تحديث: $lastUpdated", style: const TextStyle(fontSize: 10, color: Colors.grey)), const SizedBox(height: 8), SizedBox( width: double.infinity, child: ElevatedButton( onPressed: isLoading ? null : fetchData, child: const Text("تحديث البيانات"))) ], ), ), ), ], ), ); } Widget _modeBtn(String title, bool active, VoidCallback onTap) { return InkWell( onTap: onTap, child: Container( alignment: Alignment.center, padding: const EdgeInsets.symmetric(vertical: 8), decoration: BoxDecoration( color: active ? const Color(0xFF3498DB) : Colors.white, borderRadius: BorderRadius.circular(6), border: Border.all(color: const Color(0xFF3498DB))), child: Text(title, style: TextStyle( color: active ? Colors.white : const Color(0xFF3498DB), fontWeight: active ? FontWeight.bold : FontWeight.normal)), ), ); } }