chore: add build artifacts, map assets, and routing data

This commit is contained in:
Hamza-Ayed
2026-09-19 12:34:24 +03:00
parent aa2b9f131f
commit 43ad2e0ad4
327 changed files with 52421 additions and 1810 deletions
@@ -0,0 +1,338 @@
import 'package:flutter/material.dart';
import '../../../../core/constants/app_colors.dart';
import '../../../../logic/cubits/navigation/navigation_state.dart';
class ActiveNavHudWidget extends StatelessWidget {
final NavigationState state;
final VoidCallback onStopNavigation;
final VoidCallback onToggleMute;
final VoidCallback onRecenter;
const ActiveNavHudWidget({
super.key,
required this.state,
required this.onStopNavigation,
required this.onToggleMute,
required this.onRecenter,
});
IconData _getManeuverIcon(int sign) {
switch (sign) {
case -3:
return Icons.turn_sharp_left_rounded;
case -2:
return Icons.turn_left_rounded;
case -1:
return Icons.turn_slight_left_rounded;
case 1:
return Icons.turn_slight_right_rounded;
case 2:
return Icons.turn_right_rounded;
case 3:
return Icons.turn_sharp_right_rounded;
case 4:
return Icons.flag_rounded;
case 6:
return Icons.roundabout_right_rounded;
case -7:
case 7:
return Icons.u_turn_left_rounded;
case 8:
return Icons.u_turn_right_rounded;
case 0:
default:
return Icons.straight_rounded;
}
}
@override
Widget build(BuildContext context) {
return Column(
children: [
// ── TOP INSTRUCTION CARD ──
if (state.currentInstruction.isNotEmpty)
Container(
margin: const EdgeInsets.symmetric(horizontal: 16, vertical: 8),
padding: const EdgeInsets.all(16),
decoration: BoxDecoration(
color: AppColors.appleBlue,
borderRadius: BorderRadius.circular(22),
boxShadow: const [
BoxShadow(
color: Color(0x2E007AFF),
blurRadius: 20,
offset: Offset(0, 6),
),
],
),
child: Row(
children: [
// Dynamic Maneuver Direction Icon
Container(
width: 48,
height: 48,
decoration: BoxDecoration(
color: Colors.white.withValues(alpha: 0.2),
borderRadius: BorderRadius.circular(14),
),
child: Center(
child: Icon(
_getManeuverIcon(state.currentManeuverModifier),
color: Colors.white,
size: 28,
),
),
),
const SizedBox(width: 14),
Expanded(
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
if (state.distanceToNextStep > 0)
Text(
'بعد ${state.distanceToNextStep.round()} متر',
style: const TextStyle(
fontFamily: '.SF Pro Text',
fontSize: 12,
fontWeight: FontWeight.w600,
color: Colors.white70,
),
),
Text(
state.currentInstruction,
maxLines: 2,
overflow: TextOverflow.ellipsis,
style: const TextStyle(
fontFamily: '.SF Pro Text',
fontSize: 15,
fontWeight: FontWeight.w700,
color: Colors.white,
height: 1.3,
),
),
if (state.nextInstruction.isNotEmpty)
Padding(
padding: const EdgeInsets.only(top: 4),
child: Text(
'ثم ${state.nextInstruction}',
maxLines: 1,
overflow: TextOverflow.ellipsis,
style: const TextStyle(
fontFamily: '.SF Pro Text',
fontSize: 11,
fontWeight: FontWeight.w500,
color: Colors.white60,
),
),
),
],
),
),
],
),
),
const Spacer(),
// ── FLOATING SPEEDOMETER & SAFETY HUD ROW ──
Padding(
padding: const EdgeInsets.symmetric(horizontal: 18),
child: Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
crossAxisAlignment: CrossAxisAlignment.end,
children: [
// Digital Speedometer Circular HUD
_buildSpeedometerHUD(state.speed),
// Live Steep Slope Warning Pill
if (state.currentRoute != null &&
(state.currentRoute!.hasSteepSlope || state.currentRoute!.slopeWarning != null))
Container(
padding: const EdgeInsets.symmetric(horizontal: 12, vertical: 8),
margin: const EdgeInsets.only(bottom: 6),
decoration: BoxDecoration(
color: const Color(0xFFFFF3E0),
borderRadius: BorderRadius.circular(16),
border: Border.all(color: const Color(0xFFFFB74D), width: 1.2),
boxShadow: const [
BoxShadow(
color: Color(0x1F000000),
blurRadius: 12,
offset: Offset(0, 4),
),
],
),
child: const Row(
mainAxisSize: MainAxisSize.min,
children: [
Icon(Icons.warning_amber_rounded, color: Color(0xFFE65100), size: 16),
SizedBox(width: 6),
Text(
'انحدار شديد • خفف السرعة',
style: TextStyle(
fontFamily: '.SF Pro Text',
fontSize: 11,
fontWeight: FontWeight.w700,
color: Color(0xFFBF360C),
),
),
],
),
),
],
),
),
// ── BOTTOM HUD BAR ──
Container(
margin: const EdgeInsets.all(16),
padding: const EdgeInsets.all(16),
decoration: BoxDecoration(
color: AppColors.pureWhite,
borderRadius: BorderRadius.circular(26),
boxShadow: const [
BoxShadow(
color: Color(0x1F000000),
blurRadius: 24,
offset: Offset(0, 8),
),
],
border: Border.all(color: AppColors.borderSubtle),
),
child: Row(
children: [
// Trip Summary (Duration & Distance)
Expanded(
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
mainAxisSize: MainAxisSize.min,
children: [
Row(
children: [
Text(
state.formattedRemainingDuration,
style: const TextStyle(
fontFamily: '.SF Pro Text',
fontSize: 22,
fontWeight: FontWeight.w800,
color: AppColors.tacticalEmerald,
),
),
const SizedBox(width: 8),
Text(
'• ${state.formattedRemainingDistance}',
style: const TextStyle(
fontFamily: '.SF Pro Text',
fontSize: 14,
fontWeight: FontWeight.w600,
color: AppColors.textSecondary,
),
),
],
),
const SizedBox(height: 2),
Text(
'وصول متوقع: ${state.arrivalTime}',
style: const TextStyle(
fontFamily: '.SF Pro Text',
fontSize: 11,
color: AppColors.textMuted,
fontWeight: FontWeight.w500,
),
),
],
),
),
// Mute Button
IconButton(
onPressed: onToggleMute,
icon: Icon(
state.isMuted ? Icons.volume_off_rounded : Icons.volume_up_rounded,
color: state.isMuted ? AppColors.textMuted : AppColors.appleBlue,
size: 24,
),
),
// Recenter Map
IconButton(
onPressed: onRecenter,
icon: const Icon(
Icons.my_location_rounded,
color: AppColors.textSecondary,
size: 22,
),
),
const SizedBox(width: 6),
// End Navigation Button
InkWell(
onTap: onStopNavigation,
borderRadius: BorderRadius.circular(20),
child: Container(
width: 42,
height: 42,
decoration: const BoxDecoration(
color: AppColors.coralDanger,
shape: BoxShape.circle,
),
child: const Center(
child: Icon(Icons.close_rounded, color: Colors.white, size: 22),
),
),
),
],
),
),
],
);
}
Widget _buildSpeedometerHUD(double speedKmH) {
final int speed = speedKmH.clamp(0, 260).round();
final bool isHighSpeed = speed > 100;
return Container(
width: 66,
height: 66,
margin: const EdgeInsets.only(bottom: 6),
decoration: BoxDecoration(
color: AppColors.pureWhite,
shape: BoxShape.circle,
border: Border.all(
color: isHighSpeed ? AppColors.coralDanger : AppColors.appleBlue.withValues(alpha: 0.35),
width: 3.0,
),
boxShadow: const [
BoxShadow(
color: Color(0x1F000000),
blurRadius: 16,
offset: Offset(0, 6),
),
],
),
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
children: [
Text(
'$speed',
style: TextStyle(
fontFamily: '.SF Pro Text',
fontSize: 22,
fontWeight: FontWeight.w800,
height: 1.0,
color: isHighSpeed ? AppColors.coralDanger : AppColors.textPrimary,
),
),
const SizedBox(height: 2),
const Text(
'كم/س',
style: TextStyle(
fontFamily: '.SF Pro Text',
fontSize: 9,
fontWeight: FontWeight.w700,
color: AppColors.textMuted,
),
),
],
),
);
}
}
@@ -0,0 +1,127 @@
import 'package:flutter/material.dart';
import '../../../../core/constants/app_colors.dart';
class AddPlaceSheet extends StatefulWidget {
final Function(String name, String category) onSubmit;
const AddPlaceSheet({super.key, required this.onSubmit});
@override
State<AddPlaceSheet> createState() => _AddPlaceSheetState();
}
class _AddPlaceSheetState extends State<AddPlaceSheet> {
final TextEditingController _nameController = TextEditingController();
String _selectedCategory = 'restaurant';
static const List<Map<String, String>> _categories = [
{'id': 'restaurant', 'name': 'مطعم / كافيه'},
{'id': 'pharmacy', 'name': 'صيدلية / عيادة'},
{'id': 'fuel', 'name': 'محطة وقود'},
{'id': 'supermarket', 'name': 'سوبرماركت / بقالة'},
{'id': 'mosque', 'name': 'مسجد / دار عبادة'},
{'id': 'bank', 'name': 'بنك / صراف آلي'},
{'id': 'school', 'name': 'مدرسة / مؤسسة تعليمية'},
{'id': 'other', 'name': 'معلم آخر'},
];
@override
void dispose() {
_nameController.dispose();
super.dispose();
}
@override
Widget build(BuildContext context) {
return Container(
padding: EdgeInsets.fromLTRB(20, 16, 20, MediaQuery.of(context).viewInsets.bottom + 24),
decoration: const BoxDecoration(
color: AppColors.pureWhite,
borderRadius: BorderRadius.vertical(top: Radius.circular(28)),
),
child: Column(
mainAxisSize: MainAxisSize.min,
crossAxisAlignment: CrossAxisAlignment.stretch,
children: [
Center(
child: Container(
width: 36,
height: 4,
decoration: BoxDecoration(
color: AppColors.borderGlass,
borderRadius: BorderRadius.circular(2),
),
),
),
const SizedBox(height: 18),
const Text(
'إضافة مكان جديد إلى خرائط سيرو',
textAlign: TextAlign.center,
style: TextStyle(fontSize: 16, fontWeight: FontWeight.w700),
),
const SizedBox(height: 18),
TextField(
controller: _nameController,
textDirection: TextDirection.rtl,
decoration: const InputDecoration(
labelText: 'اسم المكان أو المنشأة',
labelStyle: TextStyle(fontSize: 12),
filled: true,
fillColor: AppColors.surfaceMuted,
border: OutlineInputBorder(
borderRadius: BorderRadius.all(Radius.circular(16)),
borderSide: BorderSide.none,
),
),
),
const SizedBox(height: 14),
DropdownButtonFormField<String>(
initialValue: _selectedCategory,
items: _categories.map((c) {
return DropdownMenuItem(
value: c['id'],
child: Text(c['name']!, style: const TextStyle(fontSize: 13)),
);
}).toList(),
onChanged: (v) {
if (v != null) setState(() => _selectedCategory = v);
},
decoration: const InputDecoration(
labelText: 'التصنيف',
labelStyle: TextStyle(fontSize: 12),
filled: true,
fillColor: AppColors.surfaceMuted,
border: OutlineInputBorder(
borderRadius: BorderRadius.all(Radius.circular(16)),
borderSide: BorderSide.none,
),
),
),
const SizedBox(height: 24),
SizedBox(
height: 48,
child: ElevatedButton(
onPressed: () {
final name = _nameController.text.trim();
if (name.isNotEmpty) {
widget.onSubmit(name, _selectedCategory);
Navigator.of(context).pop();
}
},
style: ElevatedButton.styleFrom(
backgroundColor: AppColors.appleBlue,
foregroundColor: Colors.white,
shape: RoundedRectangleBorder(borderRadius: BorderRadius.circular(999)),
elevation: 0,
),
child: const Text(
'حفظ المكان في الموقع المحدد',
style: TextStyle(fontSize: 14, fontWeight: FontWeight.w700),
),
),
),
],
),
);
}
}
@@ -0,0 +1,56 @@
import 'package:flutter/material.dart';
import '../../../../core/constants/app_colors.dart';
class ExplorePanelWidget extends StatelessWidget {
final Function(String query) onCategorySelected;
const ExplorePanelWidget({super.key, required this.onCategorySelected});
static const List<Map<String, dynamic>> _categories = [
{'title': 'مطاعم', 'query': 'مطعم', 'icon': Icons.restaurant_rounded},
{'title': 'كافيهات', 'query': 'مقهى', 'icon': Icons.local_cafe_rounded},
{'title': 'وقود', 'query': 'محطة وقود', 'icon': Icons.local_gas_station_rounded},
{'title': 'صيدليات', 'query': 'صيدلية', 'icon': Icons.local_pharmacy_rounded},
{'title': 'مساجد', 'query': 'مسجد', 'icon': Icons.mosque_rounded},
{'title': 'بنوك', 'query': 'بنك', 'icon': Icons.account_balance_rounded},
];
@override
Widget build(BuildContext context) {
return SizedBox(
height: 42,
child: ListView.separated(
scrollDirection: Axis.horizontal,
padding: const EdgeInsets.symmetric(horizontal: 16),
itemCount: _categories.length,
separatorBuilder: (_, __) => const SizedBox(width: 8),
itemBuilder: (context, index) {
final cat = _categories[index];
return ActionChip(
avatar: Icon(
cat['icon'] as IconData,
size: 16,
color: AppColors.appleBlue,
),
label: Text(
cat['title'] as String,
style: const TextStyle(
fontSize: 12,
fontWeight: FontWeight.w600,
color: AppColors.textPrimary,
),
),
backgroundColor: AppColors.pureWhite,
side: const BorderSide(color: AppColors.borderSubtle),
shape: RoundedRectangleBorder(
borderRadius: BorderRadius.circular(20),
),
elevation: 1,
shadowColor: Colors.black.withValues(alpha: 0.08),
onPressed: () => onCategorySelected(cat['query'] as String),
);
},
),
);
}
}
@@ -0,0 +1,113 @@
import 'package:flutter/material.dart';
import '../../../../core/constants/app_colors.dart';
import '../../../../logic/cubits/navigation/navigation_state.dart';
class LayerSelectorSheet extends StatelessWidget {
final MapThemeType currentTheme;
final ValueChanged<MapThemeType> onThemeChanged;
const LayerSelectorSheet({
super.key,
required this.currentTheme,
required this.onThemeChanged,
});
@override
Widget build(BuildContext context) {
return Container(
padding: const EdgeInsets.fromLTRB(20, 16, 20, 32),
decoration: const BoxDecoration(
color: AppColors.pureWhite,
borderRadius: BorderRadius.vertical(top: Radius.circular(28)),
),
child: Column(
mainAxisSize: MainAxisSize.min,
children: [
Container(
width: 36,
height: 4,
decoration: BoxDecoration(
color: AppColors.borderGlass,
borderRadius: BorderRadius.circular(2),
),
),
const SizedBox(height: 18),
const Text(
'طبقات الخريطة السيادية',
style: TextStyle(
fontSize: 16,
fontWeight: FontWeight.w700,
color: AppColors.textPrimary,
),
),
const SizedBox(height: 20),
Row(
mainAxisAlignment: MainAxisAlignment.spaceEvenly,
children: [
_buildTile(
type: MapThemeType.vectorLight,
title: 'متجهات نهارية',
icon: Icons.wb_sunny_rounded,
selected: currentTheme == MapThemeType.vectorLight,
),
_buildTile(
type: MapThemeType.satellite,
title: 'أقمار صناعية',
icon: Icons.satellite_alt_rounded,
selected: currentTheme == MapThemeType.satellite,
),
_buildTile(
type: MapThemeType.vectorDark,
title: 'ليلي فاخر',
icon: Icons.dark_mode_rounded,
selected: currentTheme == MapThemeType.vectorDark,
),
],
),
],
),
);
}
Widget _buildTile({
required MapThemeType type,
required String title,
required IconData icon,
required bool selected,
}) {
return InkWell(
onTap: () => onThemeChanged(type),
borderRadius: BorderRadius.circular(16),
child: Container(
width: 95,
padding: const EdgeInsets.symmetric(vertical: 14),
decoration: BoxDecoration(
color: selected ? AppColors.appleBlue.withValues(alpha: 0.08) : AppColors.surfaceMuted,
borderRadius: BorderRadius.circular(16),
border: Border.all(
color: selected ? AppColors.appleBlue : AppColors.borderSubtle,
width: selected ? 2 : 1,
),
),
child: Column(
children: [
Icon(
icon,
size: 28,
color: selected ? AppColors.appleBlue : AppColors.textSecondary,
),
const SizedBox(height: 8),
Text(
title,
style: TextStyle(
fontSize: 11,
fontWeight: selected ? FontWeight.w700 : FontWeight.w500,
color: selected ? AppColors.appleBlue : AppColors.textPrimary,
),
),
],
),
),
);
}
}
@@ -0,0 +1,148 @@
import 'package:flutter/material.dart';
import '../../../../core/constants/app_colors.dart';
class ReportHazardSheet extends StatefulWidget {
final Function(String type, String title, String description) onReport;
const ReportHazardSheet({super.key, required this.onReport});
@override
State<ReportHazardSheet> createState() => _ReportHazardSheetState();
}
class _ReportHazardSheetState extends State<ReportHazardSheet> {
String _selectedType = 'accident';
final TextEditingController _notesController = TextEditingController();
static const List<Map<String, dynamic>> _hazardTypes = [
{'type': 'accident', 'title': 'حادث سير', 'icon': Icons.car_crash_rounded},
{'type': 'police', 'title': 'دورية شرطة', 'icon': Icons.local_police_rounded},
{'type': 'closed_road', 'title': 'طريق مغلق', 'icon': Icons.block_rounded},
{'type': 'speed_bump', 'title': 'مطب صناعي', 'icon': Icons.waves_rounded},
{'type': 'camera', 'title': 'كاميرا رادار', 'icon': Icons.videocam_rounded},
{'type': 'hazard', 'title': 'خطر / عائق', 'icon': Icons.warning_rounded},
];
@override
void dispose() {
_notesController.dispose();
super.dispose();
}
@override
Widget build(BuildContext context) {
return Container(
padding: EdgeInsets.fromLTRB(20, 16, 20, MediaQuery.of(context).viewInsets.bottom + 24),
decoration: const BoxDecoration(
color: AppColors.pureWhite,
borderRadius: BorderRadius.vertical(top: Radius.circular(28)),
),
child: Column(
mainAxisSize: MainAxisSize.min,
crossAxisAlignment: CrossAxisAlignment.stretch,
children: [
Center(
child: Container(
width: 36,
height: 4,
decoration: BoxDecoration(
color: AppColors.borderGlass,
borderRadius: BorderRadius.circular(2),
),
),
),
const SizedBox(height: 18),
const Text(
'الإبلاغ عن حالة طريق أو تنبيه أمني',
textAlign: TextAlign.center,
style: TextStyle(
fontSize: 16,
fontWeight: FontWeight.w700,
color: AppColors.textPrimary,
),
),
const SizedBox(height: 6),
const Text(
'مساهمتك تساعد في تحديث المسارات وحماية حركة السير للجميع',
textAlign: TextAlign.center,
style: TextStyle(fontSize: 12, color: AppColors.textMuted),
),
const SizedBox(height: 20),
// Hazard Options Grid
Wrap(
spacing: 10,
runSpacing: 10,
alignment: WrapAlignment.center,
children: _hazardTypes.map((item) {
final isSelected = _selectedType == item['type'];
return ChoiceChip(
avatar: Icon(
item['icon'] as IconData,
size: 18,
color: isSelected ? Colors.white : AppColors.appleBlue,
),
label: Text(
item['title'] as String,
style: TextStyle(
fontSize: 12,
fontWeight: FontWeight.w600,
color: isSelected ? Colors.white : AppColors.textPrimary,
),
),
selected: isSelected,
selectedColor: AppColors.appleBlue,
backgroundColor: AppColors.surfaceMuted,
side: BorderSide(
color: isSelected ? AppColors.appleBlue : AppColors.borderSubtle,
),
onSelected: (_) => setState(() => _selectedType = item['type'] as String),
);
}).toList(),
),
const SizedBox(height: 20),
// Additional notes
TextField(
controller: _notesController,
textDirection: TextDirection.rtl,
decoration: InputDecoration(
hintText: 'ملاحظة إضافية عن الموقع (اختياري)...',
hintStyle: const TextStyle(fontSize: 12, color: AppColors.textMuted),
filled: true,
fillColor: AppColors.surfaceMuted,
border: OutlineInputBorder(
borderRadius: BorderRadius.circular(16),
borderSide: BorderSide.none,
),
),
),
const SizedBox(height: 24),
// Submit Button
SizedBox(
height: 48,
child: ElevatedButton(
onPressed: () {
final selectedItem = _hazardTypes.firstWhere((e) => e['type'] == _selectedType);
widget.onReport(
_selectedType,
selectedItem['title'] as String,
_notesController.text.trim(),
);
Navigator.of(context).pop();
},
style: ElevatedButton.styleFrom(
backgroundColor: AppColors.appleBlue,
foregroundColor: Colors.white,
shape: RoundedRectangleBorder(borderRadius: BorderRadius.circular(999)),
elevation: 0,
),
child: const Text(
'إرسال البلاغ فوراً',
style: TextStyle(fontSize: 14, fontWeight: FontWeight.w700),
),
),
),
],
),
);
}
}
@@ -0,0 +1,95 @@
import 'package:flutter/material.dart';
import '../../../../core/constants/app_colors.dart';
class SearchBarWidget extends StatelessWidget {
final TextEditingController controller;
final ValueChanged<String> onChanged;
final VoidCallback onClear;
final VoidCallback onMenuTap;
const SearchBarWidget({
super.key,
required this.controller,
required this.onChanged,
required this.onClear,
required this.onMenuTap,
});
@override
Widget build(BuildContext context) {
return Container(
height: 52,
decoration: BoxDecoration(
color: AppColors.pureWhite,
borderRadius: BorderRadius.circular(26),
boxShadow: const [
BoxShadow(
color: Color(0x14000000),
blurRadius: 16,
offset: Offset(0, 4),
),
],
border: Border.all(color: AppColors.borderSubtle),
),
child: Row(
children: [
const SizedBox(width: 14),
const Icon(Icons.search_rounded, color: AppColors.appleBlue, size: 22),
const SizedBox(width: 10),
Expanded(
child: TextField(
controller: controller,
onChanged: onChanged,
textDirection: TextDirection.rtl,
style: const TextStyle(
fontSize: 14,
color: AppColors.textPrimary,
fontWeight: FontWeight.w500,
),
decoration: const InputDecoration(
hintText: 'ابحث عن وجهة، شارع، أو منشأة (نطاق 50 كم)...',
hintStyle: TextStyle(
fontSize: 12.5,
color: AppColors.textMuted,
),
border: InputBorder.none,
isDense: true,
),
),
),
Container(
padding: const EdgeInsets.symmetric(horizontal: 6, vertical: 2),
decoration: BoxDecoration(
color: AppColors.appleBlue.withValues(alpha: 0.08),
borderRadius: BorderRadius.circular(6),
),
child: const Text(
'50 كم',
style: TextStyle(
fontSize: 9,
fontWeight: FontWeight.w600,
color: AppColors.appleBlue,
),
),
),
if (controller.text.isNotEmpty)
IconButton(
icon: const Icon(Icons.close_rounded, size: 18, color: AppColors.textMuted),
onPressed: onClear,
),
const SizedBox(width: 4),
Container(
height: 28,
width: 1,
color: AppColors.borderSubtle,
),
IconButton(
icon: const Icon(Icons.tune_rounded, color: AppColors.textSecondary, size: 20),
onPressed: onMenuTap,
),
const SizedBox(width: 4),
],
),
);
}
}