feat: add Jordan 3D topographic platform, Siro Map cinematic orbit studio, sun-shadow simulation, and sovereign intelligence security gating
This commit is contained in:
@@ -56,12 +56,12 @@ class AboutAwardsSheet extends StatelessWidget {
|
||||
shape: BoxShape.circle,
|
||||
color: AppColors.pureWhite,
|
||||
border: Border.all(
|
||||
color: AppColors.urukGold.withValues(alpha: 0.45),
|
||||
color: AppColors.appleBlue.withValues(alpha: 0.35),
|
||||
width: 2,
|
||||
),
|
||||
boxShadow: [
|
||||
BoxShadow(
|
||||
color: AppColors.urukGold.withValues(alpha: 0.2),
|
||||
color: AppColors.appleBlue.withValues(alpha: 0.15),
|
||||
blurRadius: 26,
|
||||
offset: const Offset(0, 8),
|
||||
),
|
||||
@@ -70,14 +70,14 @@ class AboutAwardsSheet extends StatelessWidget {
|
||||
padding: const EdgeInsets.all(6),
|
||||
child: ClipOval(
|
||||
child: Image.asset(
|
||||
'assets/images/uruk_prize_logo.png',
|
||||
'assets/images/siro_maps_logo.png',
|
||||
fit: BoxFit.contain,
|
||||
),
|
||||
),
|
||||
),
|
||||
const SizedBox(height: 14),
|
||||
Text(
|
||||
'جائزة أوروك الدولية',
|
||||
'خرائط سيرو',
|
||||
style: GoogleFonts.alexandria(
|
||||
fontSize: 22,
|
||||
fontWeight: FontWeight.w800,
|
||||
@@ -86,11 +86,11 @@ class AboutAwardsSheet extends StatelessWidget {
|
||||
),
|
||||
const SizedBox(height: 2),
|
||||
Text(
|
||||
'URUK INTERNATIONAL PRIZE',
|
||||
'SIRO MAP',
|
||||
style: GoogleFonts.alexandria(
|
||||
fontSize: 12,
|
||||
fontWeight: FontWeight.w700,
|
||||
color: AppColors.urukGoldDark,
|
||||
color: AppColors.appleBlue,
|
||||
letterSpacing: 1.5,
|
||||
),
|
||||
),
|
||||
@@ -98,25 +98,25 @@ class AboutAwardsSheet extends StatelessWidget {
|
||||
Container(
|
||||
padding: const EdgeInsets.symmetric(horizontal: 14, vertical: 6),
|
||||
decoration: BoxDecoration(
|
||||
color: AppColors.urukGoldLight,
|
||||
color: AppColors.surfaceMuted,
|
||||
borderRadius: BorderRadius.circular(20),
|
||||
border: Border.all(color: AppColors.urukGoldBorder),
|
||||
border: Border.all(color: AppColors.borderSubtle),
|
||||
),
|
||||
child: Row(
|
||||
mainAxisSize: MainAxisSize.min,
|
||||
children: [
|
||||
const Icon(
|
||||
Icons.workspace_premium_rounded,
|
||||
Icons.verified_rounded,
|
||||
size: 16,
|
||||
color: AppColors.urukGoldDark,
|
||||
color: AppColors.appleBlue,
|
||||
),
|
||||
const SizedBox(width: 6),
|
||||
Text(
|
||||
'رعاية ودعم الابتكار والخدمات التقنية',
|
||||
'منظومة الملاحة والخرائط الذكية المستقلة',
|
||||
style: GoogleFonts.alexandria(
|
||||
fontSize: 11.5,
|
||||
fontWeight: FontWeight.w700,
|
||||
color: AppColors.urukGoldDark,
|
||||
color: AppColors.textPrimary,
|
||||
),
|
||||
),
|
||||
],
|
||||
@@ -128,16 +128,16 @@ class AboutAwardsSheet extends StatelessWidget {
|
||||
|
||||
const SizedBox(height: 22),
|
||||
|
||||
// ── 2. URUK PRIZE MISSION & ROLE ──
|
||||
// ── 2. SIRO MAP MISSION & VISION ──
|
||||
_buildSectionCard(
|
||||
icon: Icons.lightbulb_rounded,
|
||||
iconColor: AppColors.urukGoldDark,
|
||||
title: 'دور جائزة أوروك ورسالتها التقنية',
|
||||
icon: Icons.explore_rounded,
|
||||
iconColor: AppColors.appleBlue,
|
||||
title: 'عن سيرو ماب والرؤية التقنية',
|
||||
child: Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
Text(
|
||||
'تستلهم جائزة أوروك الدولية رمزيتها من حضارة أوروك العريقة — مهد التنظيم المدني والقياسات الأولى في تاريخ الإنسانية — لتبني وتدعم المشاريع والخدمات التقنية المبتكرة في العالم العربي والشرق الأوسط.',
|
||||
'تعتمد خرائط سيرو (Siro Map) على منظومة خرائط وملاحة فيكتورية متقدمة ومستضافة ذاتياً، لتقديم حلول مكانية متطورة تخدم التنقل الذكي وسلاسل الإمداد في العالم العربي ومنطقة الشرق الأوسط وشمال أفريقيا.',
|
||||
style: GoogleFonts.alexandria(
|
||||
fontSize: 12.5,
|
||||
height: 1.65,
|
||||
@@ -147,7 +147,7 @@ class AboutAwardsSheet extends StatelessWidget {
|
||||
),
|
||||
const SizedBox(height: 10),
|
||||
Text(
|
||||
'تعمل الجائزة وبرامجها على تشجيع التوسع في البنى التحتية الرقمية، وتمكين الحلول الذكية المستقلة التي تسد الفجوات التقنية وتخدم المجتمعات وقطاعات النقل والحركة بكفاءة عالية.',
|
||||
'تهدف المنظومة إلى تمكين الاستقلالية الرقمية الجغرافية الكاملة، وتقديم خرائط عالية السرعة والدقة مع خفض جذري في تكاليف واستهلاك البيانات مقارنة بالخدمات العالمية التقليدية.',
|
||||
style: GoogleFonts.alexandria(
|
||||
fontSize: 12.5,
|
||||
height: 1.65,
|
||||
@@ -191,7 +191,7 @@ class AboutAwardsSheet extends StatelessWidget {
|
||||
const Divider(height: 18, color: AppColors.borderSubtle),
|
||||
_buildTechRow(
|
||||
icon: Icons.public_rounded,
|
||||
iconColor: AppColors.urukGoldDark,
|
||||
iconColor: AppColors.appleBlue,
|
||||
title: 'تغطية الأسواق الإقليمية الحيوية',
|
||||
desc: 'تركيز استراتيجي على توفير بيانات طرق تفصيلية ومحدثة للأسواق التي تعاني من نقص أو قيود في الخرائط العالمية (العراق، السودان، اليمن، سوريا، والمنطقة).',
|
||||
),
|
||||
|
||||
@@ -55,7 +55,7 @@ class _AddPlaceSheetState extends State<AddPlaceSheet> {
|
||||
),
|
||||
const SizedBox(height: 18),
|
||||
const Text(
|
||||
'إضافة مكان جديد إلى خرائط أوروك',
|
||||
'إضافة مكان جديد إلى خرائط سيرو',
|
||||
textAlign: TextAlign.center,
|
||||
style: TextStyle(fontSize: 16, fontWeight: FontWeight.w700),
|
||||
),
|
||||
|
||||
@@ -0,0 +1,756 @@
|
||||
import 'dart:async';
|
||||
import 'package:flutter/material.dart';
|
||||
import '../../../../core/constants/app_colors.dart';
|
||||
import '../../../../logic/cubits/navigation/navigation_cubit.dart';
|
||||
import '../../../../logic/cubits/navigation/navigation_state.dart';
|
||||
|
||||
class Cinematic3DStudioSheet extends StatefulWidget {
|
||||
final NavigationCubit cubit;
|
||||
final NavigationState state;
|
||||
|
||||
const Cinematic3DStudioSheet({
|
||||
super.key,
|
||||
required this.cubit,
|
||||
required this.state,
|
||||
});
|
||||
|
||||
@override
|
||||
State<Cinematic3DStudioSheet> createState() => _Cinematic3DStudioSheetState();
|
||||
}
|
||||
|
||||
class _Cinematic3DStudioSheetState extends State<Cinematic3DStudioSheet> {
|
||||
// Camera Tilt State
|
||||
double _tilt = 55.0;
|
||||
|
||||
// Orbit State
|
||||
bool _isOrbiting = false;
|
||||
double _orbitSpeed = 1.0;
|
||||
Timer? _orbitTimer;
|
||||
|
||||
// Sun & Shadow Simulation State
|
||||
// Range: 330 minutes (05:30 AM) to 1260 minutes (09:00 PM)
|
||||
int _sunTimeMinutes = 1110; // Default: 18:30 (Golden Hour Sunset)
|
||||
bool _isSunPlaying = false;
|
||||
Timer? _sunTimer;
|
||||
|
||||
// Layer Toggles
|
||||
bool _showBuildings = true;
|
||||
bool _showContours = true;
|
||||
bool _showHillshading = true;
|
||||
|
||||
@override
|
||||
void initState() {
|
||||
super.initState();
|
||||
// Get initial camera tilt if available
|
||||
final initialPos = widget.cubit.mapController?.cameraPosition;
|
||||
if (initialPos != null && initialPos.tilt > 0) {
|
||||
_tilt = initialPos.tilt;
|
||||
} else {
|
||||
// Set to 55 degrees for instant 3D perspective
|
||||
widget.cubit.mapController?.setTilt(55.0);
|
||||
}
|
||||
|
||||
// Apply initial golden hour lighting
|
||||
WidgetsBinding.instance.addPostFrameCallback((_) {
|
||||
_applySunLighting(_sunTimeMinutes);
|
||||
});
|
||||
}
|
||||
|
||||
@override
|
||||
void dispose() {
|
||||
_orbitTimer?.cancel();
|
||||
_sunTimer?.cancel();
|
||||
super.dispose();
|
||||
}
|
||||
|
||||
// ── Orbit Logic ──
|
||||
void _toggleOrbit() {
|
||||
setState(() {
|
||||
_isOrbiting = !_isOrbiting;
|
||||
});
|
||||
|
||||
_orbitTimer?.cancel();
|
||||
if (_isOrbiting) {
|
||||
_orbitTimer = Timer.periodic(const Duration(milliseconds: 60), (_) {
|
||||
if (!mounted || !_isOrbiting) return;
|
||||
widget.cubit.mapController?.orbitStep(
|
||||
_orbitSpeed,
|
||||
duration: const Duration(milliseconds: 60),
|
||||
);
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
// ── Sun & Shadows Simulation Logic ──
|
||||
void _toggleSunAutoPlay() {
|
||||
setState(() {
|
||||
_isSunPlaying = !_isSunPlaying;
|
||||
});
|
||||
|
||||
_sunTimer?.cancel();
|
||||
if (_isSunPlaying) {
|
||||
_sunTimer = Timer.periodic(const Duration(milliseconds: 130), (_) {
|
||||
if (!mounted || !_isSunPlaying) return;
|
||||
setState(() {
|
||||
_sunTimeMinutes += 12;
|
||||
if (_sunTimeMinutes > 1260) {
|
||||
_sunTimeMinutes = 330; // Loop back to 05:30 AM
|
||||
}
|
||||
});
|
||||
_applySunLighting(_sunTimeMinutes);
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
void _onSunSliderChanged(double value) {
|
||||
setState(() {
|
||||
_sunTimeMinutes = value.toInt();
|
||||
});
|
||||
_applySunLighting(_sunTimeMinutes);
|
||||
}
|
||||
|
||||
void _setPresetTime(int minutes) {
|
||||
setState(() {
|
||||
_sunTimeMinutes = minutes;
|
||||
});
|
||||
_applySunLighting(minutes);
|
||||
}
|
||||
|
||||
void _applySunLighting(int minutes) {
|
||||
// Fraction of daylight cycle: 0.0 (05:30) to 1.0 (21:00)
|
||||
final double fraction = ((minutes - 330) / (1260 - 330)).clamp(0.0, 1.0);
|
||||
|
||||
// Azimuth sweeps from 75° (East - Sunrise) to 180° (South - Noon) to 290° (West - Sunset)
|
||||
final double sunAzimuth = 75.0 + (fraction * 215.0);
|
||||
|
||||
String shadowColor;
|
||||
String highlightColor;
|
||||
String buildingColor;
|
||||
double opacity = 0.88;
|
||||
|
||||
if (minutes < 420) {
|
||||
// Dawn (05:30 - 07:00)
|
||||
shadowColor = '#4c1d95'; // Deep violet dawn shadow
|
||||
highlightColor = '#fed7aa'; // Peach highlight
|
||||
buildingColor = '#fbcfe8'; // Morning rose tint
|
||||
opacity = 0.85;
|
||||
} else if (minutes < 660) {
|
||||
// Morning (07:00 - 11:00)
|
||||
shadowColor = '#334155';
|
||||
highlightColor = '#ffffff';
|
||||
buildingColor = '#fef08a'; // Golden morning sun
|
||||
opacity = 0.88;
|
||||
} else if (minutes < 840) {
|
||||
// Noon (11:00 - 14:00)
|
||||
shadowColor = '#1e293b'; // Crisp direct shadow
|
||||
highlightColor = '#ffffff';
|
||||
buildingColor = '#f8fafc'; // Crisp bright daylight
|
||||
opacity = 0.90;
|
||||
} else if (minutes < 1050) {
|
||||
// Afternoon (14:00 - 17:30)
|
||||
shadowColor = '#374151';
|
||||
highlightColor = '#ffedd5';
|
||||
buildingColor = '#fed7aa'; // Warm afternoon sun
|
||||
opacity = 0.88;
|
||||
} else if (minutes < 1170) {
|
||||
// Sunset / Golden Hour (17:30 - 19:30)
|
||||
shadowColor = '#3b0764'; // Dramatic deep purple-amber shadow
|
||||
highlightColor = '#ffedd5';
|
||||
buildingColor = '#ea580c'; // Glowing warm sunset orange
|
||||
opacity = 0.92;
|
||||
} else {
|
||||
// Night / Twilight (19:30 - 21:00)
|
||||
shadowColor = '#020617';
|
||||
highlightColor = '#60a5fa'; // Cool blue moonlight
|
||||
buildingColor = '#1e293b'; // Dark sapphire buildings
|
||||
opacity = 0.80;
|
||||
}
|
||||
|
||||
widget.cubit.mapController?.updateSunSimulation(
|
||||
sunAzimuth: sunAzimuth,
|
||||
shadowColorHex: shadowColor,
|
||||
highlightColorHex: highlightColor,
|
||||
buildingColorHex: buildingColor,
|
||||
buildingOpacity: opacity,
|
||||
);
|
||||
}
|
||||
|
||||
String _formatSunTime(int minutes) {
|
||||
final int hours = minutes ~/ 60;
|
||||
final int mins = minutes % 60;
|
||||
final String minsStr = mins.toString().padLeft(2, '0');
|
||||
final String period = hours >= 12 ? 'م' : 'ص';
|
||||
final int displayHour = hours > 12 ? hours - 12 : (hours == 0 ? 12 : hours);
|
||||
|
||||
String label = '';
|
||||
if (minutes < 420) {
|
||||
label = 'الفجر والشروق 🌅';
|
||||
} else if (minutes < 660) {
|
||||
label = 'الصباح الباكر ☀️';
|
||||
} else if (minutes < 840) {
|
||||
label = 'الظهيرة الساطعة 🌤️';
|
||||
} else if (minutes < 1050) {
|
||||
label = 'العصر ⛅';
|
||||
} else if (minutes < 1170) {
|
||||
label = 'الغروب الذهبي 🌇';
|
||||
} else {
|
||||
label = 'الليل التكتيكي 🌙';
|
||||
}
|
||||
|
||||
return '$displayHour:$minsStr $period • $label';
|
||||
}
|
||||
|
||||
// ── Tilt Logic ──
|
||||
void _setTilt(double newTilt) {
|
||||
setState(() => _tilt = newTilt);
|
||||
widget.cubit.mapController?.setTilt(newTilt);
|
||||
}
|
||||
|
||||
// ── Layer Toggles ──
|
||||
void _toggleBuildings() {
|
||||
setState(() => _showBuildings = !_showBuildings);
|
||||
widget.cubit.mapController?.setLayerVisibility('building-3d', _showBuildings);
|
||||
widget.cubit.mapController?.setLayerVisibility('building-3d-osm', _showBuildings);
|
||||
}
|
||||
|
||||
void _toggleContours() {
|
||||
setState(() => _showContours = !_showContours);
|
||||
widget.cubit.mapController?.setLayerVisibility('tactical-contour-minor', _showContours);
|
||||
widget.cubit.mapController?.setLayerVisibility('tactical-contour-major', _showContours);
|
||||
widget.cubit.mapController?.setLayerVisibility('opentopo-contour-lines', _showContours);
|
||||
}
|
||||
|
||||
void _toggleHillshading() {
|
||||
setState(() => _showHillshading = !_showHillshading);
|
||||
widget.cubit.mapController?.setLayerVisibility('hillshading', _showHillshading);
|
||||
}
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
final isSatellite = widget.state.mapTheme == MapThemeType.satellite;
|
||||
final double fraction = ((_sunTimeMinutes - 330) / (1260 - 330)).clamp(0.0, 1.0);
|
||||
final double sunAzimuth = 75.0 + (fraction * 215.0);
|
||||
|
||||
return Container(
|
||||
padding: const EdgeInsets.fromLTRB(20, 16, 20, 36),
|
||||
decoration: const BoxDecoration(
|
||||
color: AppColors.pureWhite,
|
||||
borderRadius: BorderRadius.vertical(top: Radius.circular(28)),
|
||||
boxShadow: [
|
||||
BoxShadow(
|
||||
color: Color(0x28000000),
|
||||
blurRadius: 28,
|
||||
offset: Offset(0, -8),
|
||||
),
|
||||
],
|
||||
),
|
||||
child: Column(
|
||||
mainAxisSize: MainAxisSize.min,
|
||||
crossAxisAlignment: CrossAxisAlignment.stretch,
|
||||
children: [
|
||||
// Drag Handle
|
||||
Center(
|
||||
child: Container(
|
||||
width: 42,
|
||||
height: 4.5,
|
||||
decoration: BoxDecoration(
|
||||
color: AppColors.borderGlass,
|
||||
borderRadius: BorderRadius.circular(3),
|
||||
),
|
||||
),
|
||||
),
|
||||
const SizedBox(height: 14),
|
||||
|
||||
// Header
|
||||
Row(
|
||||
mainAxisAlignment: MainAxisAlignment.spaceBetween,
|
||||
children: [
|
||||
Row(
|
||||
children: [
|
||||
Container(
|
||||
width: 38,
|
||||
height: 38,
|
||||
decoration: BoxDecoration(
|
||||
gradient: const LinearGradient(
|
||||
colors: [Color(0xFFEA580C), Color(0xFFF59E0B)],
|
||||
begin: Alignment.topLeft,
|
||||
end: Alignment.bottomRight,
|
||||
),
|
||||
borderRadius: BorderRadius.circular(11),
|
||||
boxShadow: [
|
||||
BoxShadow(
|
||||
color: const Color(0xFFEA580C).withValues(alpha: 0.35),
|
||||
blurRadius: 10,
|
||||
),
|
||||
],
|
||||
),
|
||||
child: const Icon(Icons.view_in_ar_rounded, color: Colors.white, size: 22),
|
||||
),
|
||||
const SizedBox(width: 12),
|
||||
const Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
Text(
|
||||
'استوديو التضاريس والسينما 3D',
|
||||
style: TextStyle(
|
||||
fontSize: 15.5,
|
||||
fontWeight: FontWeight.w800,
|
||||
color: AppColors.textPrimary,
|
||||
),
|
||||
),
|
||||
Text(
|
||||
'دوران سينمائي • محاكاة حركة الشمس والظلال',
|
||||
style: TextStyle(fontSize: 11, color: AppColors.textSecondary),
|
||||
),
|
||||
],
|
||||
),
|
||||
],
|
||||
),
|
||||
IconButton(
|
||||
onPressed: () => Navigator.of(context).pop(),
|
||||
icon: const Icon(Icons.close_rounded, color: AppColors.textMuted),
|
||||
),
|
||||
],
|
||||
),
|
||||
|
||||
const SizedBox(height: 18),
|
||||
const Divider(height: 1, color: AppColors.borderSubtle),
|
||||
const SizedBox(height: 16),
|
||||
|
||||
// ── 1. DAY-TO-NIGHT SUN & SHADOWS SIMULATION (Stationary Location) ──
|
||||
Container(
|
||||
padding: const EdgeInsets.all(14),
|
||||
decoration: BoxDecoration(
|
||||
color: const Color(0xFF0F172A),
|
||||
borderRadius: BorderRadius.circular(18),
|
||||
border: Border.all(color: const Color(0xFF334155)),
|
||||
),
|
||||
child: Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.stretch,
|
||||
children: [
|
||||
Row(
|
||||
mainAxisAlignment: MainAxisAlignment.spaceBetween,
|
||||
children: [
|
||||
Row(
|
||||
children: [
|
||||
Container(
|
||||
padding: const EdgeInsets.all(6),
|
||||
decoration: BoxDecoration(
|
||||
color: const Color(0xFFEA580C).withValues(alpha: 0.2),
|
||||
borderRadius: BorderRadius.circular(8),
|
||||
),
|
||||
child: const Icon(Icons.wb_sunny_rounded, color: Color(0xFFFB923C), size: 16),
|
||||
),
|
||||
const SizedBox(width: 8),
|
||||
const Text(
|
||||
'محاكاة حركة الشمس وسقوط الظلال',
|
||||
style: TextStyle(
|
||||
fontSize: 13,
|
||||
fontWeight: FontWeight.w700,
|
||||
color: Colors.white,
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
// Auto-Play Toggle Button
|
||||
InkWell(
|
||||
onTap: _toggleSunAutoPlay,
|
||||
borderRadius: BorderRadius.circular(8),
|
||||
child: Container(
|
||||
padding: const EdgeInsets.symmetric(horizontal: 10, vertical: 5),
|
||||
decoration: BoxDecoration(
|
||||
color: _isSunPlaying
|
||||
? const Color(0xFFEA580C)
|
||||
: Colors.white.withValues(alpha: 0.1),
|
||||
borderRadius: BorderRadius.circular(8),
|
||||
border: Border.all(
|
||||
color: _isSunPlaying
|
||||
? const Color(0xFFEA580C)
|
||||
: Colors.white.withValues(alpha: 0.2),
|
||||
),
|
||||
),
|
||||
child: Row(
|
||||
mainAxisSize: MainAxisSize.min,
|
||||
children: [
|
||||
Icon(
|
||||
_isSunPlaying ? Icons.pause_rounded : Icons.play_arrow_rounded,
|
||||
size: 14,
|
||||
color: Colors.white,
|
||||
),
|
||||
const SizedBox(width: 4),
|
||||
Text(
|
||||
_isSunPlaying ? 'إيقاف الدورة' : 'دورة تلقائية',
|
||||
style: const TextStyle(
|
||||
fontSize: 11,
|
||||
fontWeight: FontWeight.w700,
|
||||
color: Colors.white,
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
const SizedBox(height: 6),
|
||||
const Text(
|
||||
'المكان ثابت: وحركة الضوء تحاكي زاوية الشمس لرؤية تشكل وظلال المباني والجبال',
|
||||
style: TextStyle(fontSize: 10.5, color: Color(0xFF94A3B8)),
|
||||
),
|
||||
const SizedBox(height: 12),
|
||||
|
||||
// Current Time Display & Azimuth
|
||||
Row(
|
||||
mainAxisAlignment: MainAxisAlignment.spaceBetween,
|
||||
children: [
|
||||
Text(
|
||||
_formatSunTime(_sunTimeMinutes),
|
||||
style: const TextStyle(
|
||||
fontSize: 12.5,
|
||||
fontWeight: FontWeight.w800,
|
||||
color: Color(0xFFFDBA74),
|
||||
),
|
||||
),
|
||||
Container(
|
||||
padding: const EdgeInsets.symmetric(horizontal: 7, vertical: 2),
|
||||
decoration: BoxDecoration(
|
||||
color: Colors.white.withValues(alpha: 0.08),
|
||||
borderRadius: BorderRadius.circular(6),
|
||||
),
|
||||
child: Text(
|
||||
'زاوية الإسقاط: ${sunAzimuth.toStringAsFixed(0)}°',
|
||||
style: const TextStyle(fontSize: 10, color: Color(0xFFCBD5E1)),
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
const SizedBox(height: 6),
|
||||
|
||||
// Slider
|
||||
SliderTheme(
|
||||
data: SliderTheme.of(context).copyWith(
|
||||
activeTrackColor: const Color(0xFFEA580C),
|
||||
inactiveTrackColor: Colors.white.withValues(alpha: 0.15),
|
||||
thumbColor: const Color(0xFFFB923C),
|
||||
overlayColor: const Color(0xFFEA580C).withValues(alpha: 0.2),
|
||||
trackHeight: 4,
|
||||
),
|
||||
child: Slider(
|
||||
min: 330.0,
|
||||
max: 1260.0,
|
||||
value: _sunTimeMinutes.toDouble(),
|
||||
onChanged: _onSunSliderChanged,
|
||||
),
|
||||
),
|
||||
|
||||
// Quick Sun Period Buttons
|
||||
Row(
|
||||
mainAxisAlignment: MainAxisAlignment.spaceAround,
|
||||
children: [
|
||||
_buildSunPeriodChip('06:00 ص', 'الفجر', 360),
|
||||
_buildSunPeriodChip('09:00 ص', 'الصباح', 540),
|
||||
_buildSunPeriodChip('12:00 م', 'الظهيرة', 720),
|
||||
_buildSunPeriodChip('06:30 م', 'الغروب', 1110),
|
||||
_buildSunPeriodChip('08:30 م', 'الليل', 1230),
|
||||
],
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
|
||||
const SizedBox(height: 16),
|
||||
|
||||
// ── 2. CINEMATIC 360° ORBIT & CAMERA TILT ──
|
||||
Row(
|
||||
children: [
|
||||
// Orbit Button
|
||||
Expanded(
|
||||
child: InkWell(
|
||||
onTap: _toggleOrbit,
|
||||
borderRadius: BorderRadius.circular(16),
|
||||
child: Container(
|
||||
padding: const EdgeInsets.symmetric(vertical: 13, horizontal: 12),
|
||||
decoration: BoxDecoration(
|
||||
color: _isOrbiting
|
||||
? AppColors.appleBlue.withValues(alpha: 0.1)
|
||||
: AppColors.surfaceMuted,
|
||||
borderRadius: BorderRadius.circular(16),
|
||||
border: Border.all(
|
||||
color: _isOrbiting ? AppColors.appleBlue : AppColors.borderSubtle,
|
||||
width: _isOrbiting ? 2 : 1,
|
||||
),
|
||||
),
|
||||
child: Row(
|
||||
mainAxisAlignment: MainAxisAlignment.center,
|
||||
children: [
|
||||
Icon(
|
||||
Icons.rotate_right_rounded,
|
||||
color: _isOrbiting ? AppColors.appleBlue : AppColors.textPrimary,
|
||||
size: 20,
|
||||
),
|
||||
const SizedBox(width: 8),
|
||||
Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
Text(
|
||||
_isOrbiting ? 'الدوران نشط 360°' : 'دوران سينمائي',
|
||||
style: TextStyle(
|
||||
fontSize: 12.5,
|
||||
fontWeight: FontWeight.w700,
|
||||
color: _isOrbiting ? AppColors.appleBlue : AppColors.textPrimary,
|
||||
),
|
||||
),
|
||||
Text(
|
||||
_isOrbiting ? 'انقر للإيقاف' : 'التفاف حول الموقع',
|
||||
style: const TextStyle(fontSize: 10, color: AppColors.textMuted),
|
||||
),
|
||||
],
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
const SizedBox(width: 10),
|
||||
|
||||
// Orbit Speed Selector (When active)
|
||||
if (_isOrbiting)
|
||||
Container(
|
||||
padding: const EdgeInsets.symmetric(horizontal: 8, vertical: 6),
|
||||
decoration: BoxDecoration(
|
||||
color: AppColors.surfaceMuted,
|
||||
borderRadius: BorderRadius.circular(14),
|
||||
border: Border.all(color: AppColors.borderSubtle),
|
||||
),
|
||||
child: Row(
|
||||
children: [
|
||||
_buildSpeedChip(0.6, 'بطيء'),
|
||||
_buildSpeedChip(1.2, 'سلس'),
|
||||
_buildSpeedChip(2.4, 'سريع'),
|
||||
],
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
|
||||
const SizedBox(height: 14),
|
||||
|
||||
// ── 3. CAMERA TILT CONTROLS ──
|
||||
Container(
|
||||
padding: const EdgeInsets.symmetric(horizontal: 14, vertical: 12),
|
||||
decoration: BoxDecoration(
|
||||
color: AppColors.surfaceMuted,
|
||||
borderRadius: BorderRadius.circular(16),
|
||||
border: Border.all(color: AppColors.borderSubtle),
|
||||
),
|
||||
child: Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
Row(
|
||||
mainAxisAlignment: MainAxisAlignment.spaceBetween,
|
||||
children: [
|
||||
const Text(
|
||||
'إمالة الكاميرا للأفق (Camera Tilt):',
|
||||
style: TextStyle(
|
||||
fontSize: 12.5,
|
||||
fontWeight: FontWeight.w700,
|
||||
color: AppColors.textPrimary,
|
||||
),
|
||||
),
|
||||
Text(
|
||||
'${_tilt.toStringAsFixed(0)}°',
|
||||
style: const TextStyle(
|
||||
fontSize: 13,
|
||||
fontWeight: FontWeight.w800,
|
||||
color: AppColors.appleBlue,
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
const SizedBox(height: 8),
|
||||
Row(
|
||||
children: [
|
||||
_buildTiltPresetChip(0.0, '2D مسطح (0°)'),
|
||||
const SizedBox(width: 8),
|
||||
_buildTiltPresetChip(45.0, '3D كتل (45°)'),
|
||||
const SizedBox(width: 8),
|
||||
_buildTiltPresetChip(60.0, 'أفق سينمائي (60°)'),
|
||||
],
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
|
||||
const SizedBox(height: 14),
|
||||
|
||||
// ── 4. 3D LAYERS TOGGLES ──
|
||||
Row(
|
||||
mainAxisAlignment: MainAxisAlignment.spaceEvenly,
|
||||
children: [
|
||||
_buildFeatureToggle(
|
||||
title: 'المباني 3D',
|
||||
icon: Icons.apartment_rounded,
|
||||
active: _showBuildings,
|
||||
onTap: _toggleBuildings,
|
||||
),
|
||||
_buildFeatureToggle(
|
||||
title: 'خطوط الكنتور',
|
||||
icon: Icons.layers_outlined,
|
||||
active: _showContours,
|
||||
onTap: _toggleContours,
|
||||
),
|
||||
_buildFeatureToggle(
|
||||
title: 'ظلال التضاريس',
|
||||
icon: Icons.landscape_rounded,
|
||||
active: _showHillshading,
|
||||
onTap: _toggleHillshading,
|
||||
),
|
||||
_buildFeatureToggle(
|
||||
title: 'أقمار صناعية',
|
||||
icon: Icons.satellite_alt_rounded,
|
||||
active: isSatellite,
|
||||
onTap: () {
|
||||
widget.cubit.setMapTheme(
|
||||
isSatellite ? MapThemeType.vectorLight : MapThemeType.satellite,
|
||||
);
|
||||
},
|
||||
),
|
||||
],
|
||||
),
|
||||
],
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
Widget _buildSunPeriodChip(String time, String label, int minutes) {
|
||||
final bool isSelected = (_sunTimeMinutes - minutes).abs() < 40;
|
||||
return InkWell(
|
||||
onTap: () => _setPresetTime(minutes),
|
||||
borderRadius: BorderRadius.circular(6),
|
||||
child: Container(
|
||||
padding: const EdgeInsets.symmetric(horizontal: 6, vertical: 3),
|
||||
decoration: BoxDecoration(
|
||||
color: isSelected
|
||||
? const Color(0xFFEA580C)
|
||||
: Colors.white.withValues(alpha: 0.08),
|
||||
borderRadius: BorderRadius.circular(6),
|
||||
),
|
||||
child: Column(
|
||||
children: [
|
||||
Text(
|
||||
label,
|
||||
style: TextStyle(
|
||||
fontSize: 9.5,
|
||||
fontWeight: isSelected ? FontWeight.w800 : FontWeight.w500,
|
||||
color: isSelected ? Colors.white : const Color(0xFFCBD5E1),
|
||||
),
|
||||
),
|
||||
Text(
|
||||
time,
|
||||
style: TextStyle(
|
||||
fontSize: 8.5,
|
||||
color: isSelected ? Colors.white.withValues(alpha: 0.9) : const Color(0xFF64748B),
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
Widget _buildSpeedChip(double speed, String label) {
|
||||
final bool isSelected = (_orbitSpeed - speed).abs() < 0.1;
|
||||
return InkWell(
|
||||
onTap: () => setState(() => _orbitSpeed = speed),
|
||||
borderRadius: BorderRadius.circular(8),
|
||||
child: Container(
|
||||
padding: const EdgeInsets.symmetric(horizontal: 7, vertical: 5),
|
||||
decoration: BoxDecoration(
|
||||
color: isSelected ? AppColors.appleBlue : Colors.transparent,
|
||||
borderRadius: BorderRadius.circular(8),
|
||||
),
|
||||
child: Text(
|
||||
label,
|
||||
style: TextStyle(
|
||||
fontSize: 10.5,
|
||||
fontWeight: isSelected ? FontWeight.w700 : FontWeight.w500,
|
||||
color: isSelected ? Colors.white : AppColors.textSecondary,
|
||||
),
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
Widget _buildTiltPresetChip(double tiltValue, String label) {
|
||||
final bool isSelected = (_tilt - tiltValue).abs() < 4.0;
|
||||
return Expanded(
|
||||
child: InkWell(
|
||||
onTap: () => _setTilt(tiltValue),
|
||||
borderRadius: BorderRadius.circular(10),
|
||||
child: Container(
|
||||
padding: const EdgeInsets.symmetric(vertical: 8),
|
||||
decoration: BoxDecoration(
|
||||
color: isSelected ? AppColors.appleBlue : Colors.white,
|
||||
borderRadius: BorderRadius.circular(10),
|
||||
border: Border.all(
|
||||
color: isSelected ? AppColors.appleBlue : AppColors.borderSubtle,
|
||||
),
|
||||
),
|
||||
child: Text(
|
||||
label,
|
||||
textAlign: TextAlign.center,
|
||||
style: TextStyle(
|
||||
fontSize: 11,
|
||||
fontWeight: isSelected ? FontWeight.w700 : FontWeight.w600,
|
||||
color: isSelected ? Colors.white : AppColors.textPrimary,
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
Widget _buildFeatureToggle({
|
||||
required String title,
|
||||
required IconData icon,
|
||||
required bool active,
|
||||
required VoidCallback onTap,
|
||||
}) {
|
||||
return InkWell(
|
||||
onTap: onTap,
|
||||
borderRadius: BorderRadius.circular(12),
|
||||
child: Container(
|
||||
width: 76,
|
||||
padding: const EdgeInsets.symmetric(vertical: 9),
|
||||
decoration: BoxDecoration(
|
||||
color: active
|
||||
? AppColors.appleBlue.withValues(alpha: 0.08)
|
||||
: AppColors.surfaceMuted,
|
||||
borderRadius: BorderRadius.circular(12),
|
||||
border: Border.all(
|
||||
color: active ? AppColors.appleBlue : AppColors.borderSubtle,
|
||||
width: active ? 1.5 : 1,
|
||||
),
|
||||
),
|
||||
child: Column(
|
||||
children: [
|
||||
Icon(
|
||||
icon,
|
||||
size: 20,
|
||||
color: active ? AppColors.appleBlue : AppColors.textMuted,
|
||||
),
|
||||
const SizedBox(height: 5),
|
||||
Text(
|
||||
title,
|
||||
style: TextStyle(
|
||||
fontSize: 10,
|
||||
fontWeight: active ? FontWeight.w700 : FontWeight.w500,
|
||||
color: active ? AppColors.appleBlue : AppColors.textSecondary,
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -7,6 +7,7 @@ class LayerSelectorSheet extends StatelessWidget {
|
||||
final ValueChanged<MapThemeType> onThemeChanged;
|
||||
final VoidCallback? onOpenVehicleCustomizer;
|
||||
final VoidCallback? onOpenAboutAwards;
|
||||
final VoidCallback? onOpenCinematic3DStudio;
|
||||
|
||||
const LayerSelectorSheet({
|
||||
super.key,
|
||||
@@ -14,6 +15,7 @@ class LayerSelectorSheet extends StatelessWidget {
|
||||
required this.onThemeChanged,
|
||||
this.onOpenVehicleCustomizer,
|
||||
this.onOpenAboutAwards,
|
||||
this.onOpenCinematic3DStudio,
|
||||
});
|
||||
|
||||
@override
|
||||
@@ -68,6 +70,43 @@ class LayerSelectorSheet extends StatelessWidget {
|
||||
),
|
||||
],
|
||||
),
|
||||
if (onOpenCinematic3DStudio != null) ...[
|
||||
const SizedBox(height: 16),
|
||||
ListTile(
|
||||
onTap: onOpenCinematic3DStudio,
|
||||
shape: RoundedRectangleBorder(
|
||||
borderRadius: BorderRadius.circular(16),
|
||||
side: const BorderSide(color: Color(0x33EA580C)),
|
||||
),
|
||||
tileColor: const Color(0xFFEA580C).withValues(alpha: 0.08),
|
||||
leading: Container(
|
||||
width: 40,
|
||||
height: 40,
|
||||
decoration: BoxDecoration(
|
||||
gradient: const LinearGradient(
|
||||
colors: [Color(0xFFEA580C), Color(0xFFF59E0B)],
|
||||
begin: Alignment.topLeft,
|
||||
end: Alignment.bottomRight,
|
||||
),
|
||||
borderRadius: BorderRadius.circular(12),
|
||||
),
|
||||
child: const Icon(Icons.view_in_ar_rounded, color: Colors.white, size: 22),
|
||||
),
|
||||
title: const Text(
|
||||
'استوديو التضاريس والسينما 3D',
|
||||
style: TextStyle(
|
||||
fontSize: 14,
|
||||
fontWeight: FontWeight.w800,
|
||||
color: AppColors.textPrimary,
|
||||
),
|
||||
),
|
||||
subtitle: const Text(
|
||||
'دوران سينمائي 360° • محاكاة حركة الشمس وسقوط الظلال',
|
||||
style: TextStyle(fontSize: 11, color: Color(0xFFEA580C), fontWeight: FontWeight.w600),
|
||||
),
|
||||
trailing: const Icon(Icons.arrow_forward_ios_rounded, size: 14, color: Color(0xFFEA580C)),
|
||||
),
|
||||
],
|
||||
if (onOpenVehicleCustomizer != null) ...[
|
||||
const SizedBox(height: 18),
|
||||
const Divider(height: 1, color: AppColors.borderSubtle),
|
||||
@@ -106,37 +145,37 @@ class LayerSelectorSheet extends StatelessWidget {
|
||||
onTap: onOpenAboutAwards,
|
||||
shape: RoundedRectangleBorder(
|
||||
borderRadius: BorderRadius.circular(16),
|
||||
side: const BorderSide(color: AppColors.urukGoldBorder),
|
||||
side: const BorderSide(color: AppColors.borderSubtle),
|
||||
),
|
||||
tileColor: AppColors.urukGoldLight,
|
||||
tileColor: AppColors.surfaceMuted,
|
||||
leading: Container(
|
||||
width: 40,
|
||||
height: 40,
|
||||
decoration: BoxDecoration(
|
||||
color: AppColors.pureWhite,
|
||||
shape: BoxShape.circle,
|
||||
border: Border.all(color: AppColors.urukGold.withValues(alpha: 0.4)),
|
||||
border: Border.all(color: AppColors.borderSubtle),
|
||||
),
|
||||
child: ClipOval(
|
||||
child: Image.asset(
|
||||
'assets/images/uruk_prize_logo.png',
|
||||
'assets/images/siro_maps_logo.png',
|
||||
fit: BoxFit.contain,
|
||||
),
|
||||
),
|
||||
),
|
||||
title: const Text(
|
||||
'عن منظومة أوروك والتقنيات المفعلة',
|
||||
'عن منظومة سيرو ماب والتقنيات المفعلة',
|
||||
style: TextStyle(
|
||||
fontSize: 13.5,
|
||||
fontWeight: FontWeight.w700,
|
||||
color: AppColors.urukGoldDark,
|
||||
color: AppColors.textPrimary,
|
||||
),
|
||||
),
|
||||
subtitle: const Text(
|
||||
'الكاش الذكي، مفاتيح الأجهزة، والملاحة دون اتصال',
|
||||
style: TextStyle(fontSize: 11, color: AppColors.textSecondary),
|
||||
),
|
||||
trailing: const Icon(Icons.workspace_premium_rounded, size: 20, color: AppColors.urukGoldDark),
|
||||
trailing: const Icon(Icons.info_outline_rounded, size: 20, color: AppColors.appleBlue),
|
||||
),
|
||||
],
|
||||
],
|
||||
|
||||
Reference in New Issue
Block a user