Files
maps-saas/packages/tactical_app/lib/widgets/tactical_isochrone_sheet.dart
T

252 lines
9.4 KiB
Dart

import 'package:flutter/material.dart';
import 'package:intaleq_maps/intaleq_maps.dart';
import '../services/military_grid_utils.dart';
import '../services/tactical_isochrone_engine.dart';
/// Dedicated Tactical Isochrone & QRF Reachability HUD Sheet
class TacticalIsochroneSheet extends StatefulWidget {
final LatLng? center;
final VoidCallback onPickCenter;
final VoidCallback onClose;
final Function(List<IsochroneRing>) onIsochronesCalculated;
const TacticalIsochroneSheet({
super.key,
required this.center,
required this.onPickCenter,
required this.onClose,
required this.onIsochronesCalculated,
});
@override
State<TacticalIsochroneSheet> createState() => _TacticalIsochroneSheetState();
}
class _TacticalIsochroneSheetState extends State<TacticalIsochroneSheet> {
double _speedKmh = 60.0;
bool _isLoading = false;
List<IsochroneRing>? _rings;
@override
void initState() {
super.initState();
_recalculate();
}
@override
void didUpdateWidget(covariant TacticalIsochroneSheet oldWidget) {
super.didUpdateWidget(oldWidget);
if (widget.center != oldWidget.center) {
_recalculate();
}
}
Future<void> _recalculate() async {
if (widget.center == null) {
setState(() => _rings = null);
return;
}
setState(() => _isLoading = true);
try {
final rings = await TacticalIsochroneEngine.calculateIsochrones(
center: widget.center!,
baseSpeedKmh: _speedKmh,
);
if (mounted) {
setState(() {
_rings = rings;
_isLoading = false;
});
widget.onIsochronesCalculated(rings);
}
} catch (e) {
if (mounted) setState(() => _isLoading = false);
}
}
@override
Widget build(BuildContext context) {
return Container(
decoration: const BoxDecoration(
color: Color(0xFF090E1A),
borderRadius: BorderRadius.vertical(top: Radius.circular(20)),
border: Border(top: BorderSide(color: Color(0xFFA855F7), width: 1.5)),
boxShadow: [
BoxShadow(color: Colors.black87, blurRadius: 24, spreadRadius: 4),
],
),
child: SafeArea(
top: false,
child: SingleChildScrollView(
padding: const EdgeInsets.all(16),
child: Column(
mainAxisSize: MainAxisSize.min,
crossAxisAlignment: CrossAxisAlignment.stretch,
children: [
// ── Header ───────────────────────────────────────────
Row(
children: [
Container(
padding: const EdgeInsets.all(8),
decoration: BoxDecoration(
color: const Color(0xFFA855F7).withAlpha(40),
borderRadius: BorderRadius.circular(10),
border: Border.all(color: const Color(0xFFA855F7)),
),
child: const Icon(Icons.timelapse, color: Color(0xFFC084FC), size: 20),
),
const SizedBox(width: 10),
const Expanded(
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Text(
'نطاق التدخل السريع وزمن الاستجابة (QRF Isochrone)',
style: TextStyle(
color: Colors.white,
fontSize: 14,
fontWeight: FontWeight.bold,
),
),
Text(
'حساب مضلعات الوصول خلال 5 و 10 و 15 دقيقة مع تأثير التضاريس',
style: TextStyle(color: Color(0xFF94A3B8), fontSize: 10),
),
],
),
),
IconButton(
icon: const Icon(Icons.close, color: Colors.white70, size: 20),
onPressed: widget.onClose,
),
],
),
const SizedBox(height: 14),
// ── Center Picker Card ────────────────────────────────
InkWell(
onTap: widget.onPickCenter,
borderRadius: BorderRadius.circular(10),
child: Container(
padding: const EdgeInsets.all(12),
decoration: BoxDecoration(
color: const Color(0xFF0F172A),
borderRadius: BorderRadius.circular(10),
border: Border.all(
color: widget.center != null
? const Color(0xFFA855F7)
: Colors.white12,
),
),
child: Row(
children: [
const Icon(Icons.my_location, color: Color(0xFFC084FC), size: 18),
const SizedBox(width: 8),
Expanded(
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
const Text('موقع قاعدة الانطلاق / قوة التدخل:', style: TextStyle(color: Color(0xFF94A3B8), fontSize: 10)),
const SizedBox(height: 2),
Text(
widget.center != null
? MilitaryGridUtils.latLngToMgrs(
widget.center!.latitude,
widget.center!.longitude,
)
: 'انقر لتحديد موقع الانطلاق على الخريطة...',
style: TextStyle(
color: widget.center != null ? Colors.white : Colors.white54,
fontSize: 12,
fontFamily: 'monospace',
fontWeight: FontWeight.bold,
),
),
],
),
),
const Icon(Icons.touch_app, color: Color(0xFFC084FC), size: 18),
],
),
),
),
const SizedBox(height: 12),
// ── Speed Slider ──────────────────────────────────────
Row(
children: [
const Icon(Icons.speed, color: Color(0xFF38BDF8), size: 16),
const SizedBox(width: 6),
Text(
'متوسط سرعة الحركة: ${_speedKmh.toInt()} كم/ساعة',
style: const TextStyle(color: Colors.white, fontSize: 11, fontWeight: FontWeight.bold),
),
],
),
Slider(
value: _speedKmh,
min: 20,
max: 100,
divisions: 16,
activeColor: const Color(0xFFA855F7),
inactiveColor: const Color(0xFF1E293B),
onChanged: (v) {
setState(() => _speedKmh = v);
_recalculate();
},
),
// ── Ring Legend ───────────────────────────────────────
if (_isLoading)
const Center(
child: Padding(
padding: EdgeInsets.all(16),
child: CircularProgressIndicator(color: Color(0xFFA855F7)),
),
)
else if (_rings != null) ...[
Row(
children: _rings!.map((r) {
return Expanded(
child: Container(
margin: const EdgeInsets.symmetric(horizontal: 3),
padding: const EdgeInsets.all(16),
decoration: BoxDecoration(
color: r.ringColor.withAlpha(25),
borderRadius: BorderRadius.circular(8),
border: Border.all(color: r.ringColor),
),
child: Column(
children: [
Text(
'${r.timeMinutes} دقائق',
style: TextStyle(
color: r.ringColor,
fontSize: 11.5,
fontWeight: FontWeight.bold,
),
),
Text(
'نطاق ≈ ${r.distanceKm} كم',
style: const TextStyle(color: Colors.white70, fontSize: 9.5),
),
],
),
),
);
}).toList(),
),
],
],
),
),
),
);
}
}