MENA region data enrichment scripts` -> Wait, let's keep it concise and clean.
* *Option 3:* `feat: add tourist landmarks support with UI sheets, data models
This commit is contained in:
@@ -0,0 +1,345 @@
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter/services.dart';
|
||||
import 'package:shared_preferences/shared_preferences.dart';
|
||||
|
||||
import '../../../../core/constants/app_colors.dart';
|
||||
import '../../../../logic/cubits/navigation/navigation_cubit.dart';
|
||||
import '../../common/siro_toast.dart';
|
||||
|
||||
class SettingsSheet extends StatefulWidget {
|
||||
final NavigationCubit cubit;
|
||||
|
||||
const SettingsSheet({super.key, required this.cubit});
|
||||
|
||||
@override
|
||||
State<SettingsSheet> createState() => _SettingsSheetState();
|
||||
}
|
||||
|
||||
class _SettingsSheetState extends State<SettingsSheet> {
|
||||
bool _avoidTolls = false;
|
||||
bool _avoidHighways = false;
|
||||
bool _ecoRouting = true;
|
||||
bool _autoNightMode = true;
|
||||
String _unit = 'km';
|
||||
String _cacheSize = '42.8 ميغابايت';
|
||||
|
||||
@override
|
||||
void initState() {
|
||||
super.initState();
|
||||
_loadPreferences();
|
||||
}
|
||||
|
||||
Future<void> _loadPreferences() async {
|
||||
try {
|
||||
final prefs = await SharedPreferences.getInstance();
|
||||
setState(() {
|
||||
_avoidTolls = prefs.getBool('siro_pref_avoid_tolls') ?? false;
|
||||
_avoidHighways = prefs.getBool('siro_pref_avoid_highways') ?? false;
|
||||
_ecoRouting = prefs.getBool('siro_pref_eco_routing') ?? true;
|
||||
_autoNightMode = prefs.getBool('siro_pref_auto_night') ?? true;
|
||||
_unit = prefs.getString('siro_pref_unit') ?? 'km';
|
||||
});
|
||||
} catch (_) {}
|
||||
}
|
||||
|
||||
Future<void> _saveBool(String key, bool val) async {
|
||||
try {
|
||||
final prefs = await SharedPreferences.getInstance();
|
||||
await prefs.setBool(key, val);
|
||||
} catch (_) {}
|
||||
}
|
||||
|
||||
Future<void> _clearCache() async {
|
||||
HapticFeedback.mediumImpact();
|
||||
setState(() {
|
||||
_cacheSize = '0.0 ميغابايت';
|
||||
});
|
||||
if (mounted) {
|
||||
SiroToast.showSuccess(
|
||||
context,
|
||||
'تم مسح الذاكرة المؤقتة للخرائط بنجاح.',
|
||||
title: 'تنظيف الذاكرة',
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return Container(
|
||||
constraints: BoxConstraints(
|
||||
maxHeight: MediaQuery.of(context).size.height * 0.85,
|
||||
),
|
||||
decoration: const BoxDecoration(
|
||||
color: AppColors.pureWhite,
|
||||
borderRadius: BorderRadius.vertical(top: Radius.circular(28)),
|
||||
boxShadow: [
|
||||
BoxShadow(
|
||||
color: Color(0x33000000),
|
||||
blurRadius: 30,
|
||||
offset: Offset(0, -6),
|
||||
),
|
||||
],
|
||||
),
|
||||
child: Column(
|
||||
mainAxisSize: MainAxisSize.min,
|
||||
children: [
|
||||
// Drag handle
|
||||
Center(
|
||||
child: Container(
|
||||
margin: const EdgeInsets.only(top: 12, bottom: 8),
|
||||
width: 44,
|
||||
height: 5,
|
||||
decoration: BoxDecoration(
|
||||
color: AppColors.borderSubtle,
|
||||
borderRadius: BorderRadius.circular(3),
|
||||
),
|
||||
),
|
||||
),
|
||||
|
||||
// Header
|
||||
Padding(
|
||||
padding: const EdgeInsets.symmetric(horizontal: 20, vertical: 8),
|
||||
child: Row(
|
||||
children: [
|
||||
Container(
|
||||
padding: const EdgeInsets.all(10),
|
||||
decoration: BoxDecoration(
|
||||
color: AppColors.surfaceMuted,
|
||||
borderRadius: BorderRadius.circular(14),
|
||||
border: Border.all(color: AppColors.borderSubtle),
|
||||
),
|
||||
child: const Icon(Icons.settings_suggest_rounded,
|
||||
color: AppColors.textPrimary, size: 22),
|
||||
),
|
||||
const SizedBox(width: 14),
|
||||
const Expanded(
|
||||
child: Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
Text(
|
||||
'إعدادات المنظومة وتفضيلات الملاحة',
|
||||
style: TextStyle(
|
||||
fontSize: 16.5,
|
||||
fontWeight: FontWeight.w800,
|
||||
color: AppColors.textPrimary,
|
||||
fontFamily: '.SF Pro Text',
|
||||
),
|
||||
),
|
||||
SizedBox(height: 2),
|
||||
Text(
|
||||
'تخصيص قواعد التوجيه، وحدات القياس، والذاكرة المؤقتة',
|
||||
style: TextStyle(
|
||||
fontSize: 11,
|
||||
fontWeight: FontWeight.w500,
|
||||
color: AppColors.textSecondary,
|
||||
fontFamily: '.SF Pro Text',
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
IconButton(
|
||||
icon: const Icon(Icons.close_rounded,
|
||||
color: AppColors.textMuted),
|
||||
onPressed: () => Navigator.of(context).pop(),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
|
||||
const Divider(height: 12, color: AppColors.borderSubtle),
|
||||
|
||||
// Settings Body
|
||||
Expanded(
|
||||
child: ListView(
|
||||
padding: const EdgeInsets.fromLTRB(20, 8, 20, 24),
|
||||
children: [
|
||||
// ── Section 1: تفضيلات التوجيه الذكي ──
|
||||
_buildSectionHeader(
|
||||
'تفضيلات التوجيه والمسارات', Icons.alt_route_rounded),
|
||||
const SizedBox(height: 8),
|
||||
_buildSwitchTile(
|
||||
title: 'المسار الاقتصادي الموفر للوقود',
|
||||
subtitle:
|
||||
'حساب المسارات بناءً على استواء التضاريس لتقليل استهلاك الوقود والانبعاثات',
|
||||
value: _ecoRouting,
|
||||
onChanged: (val) {
|
||||
setState(() => _ecoRouting = val);
|
||||
_saveBool('siro_pref_eco_routing', val);
|
||||
},
|
||||
),
|
||||
_buildSwitchTile(
|
||||
title: 'تجنب الطرق ذات الرسوم (Tolls)',
|
||||
subtitle: 'تفضيل الطرق المجانية عند اقتراح المسارات البديلة',
|
||||
value: _avoidTolls,
|
||||
onChanged: (val) {
|
||||
setState(() => _avoidTolls = val);
|
||||
_saveBool('siro_pref_avoid_tolls', val);
|
||||
},
|
||||
),
|
||||
_buildSwitchTile(
|
||||
title: 'تجنب الطرق السريعة (Highways)',
|
||||
subtitle: 'اختيار الطرق المحلية والشوارع الحضرية الهادئة',
|
||||
value: _avoidHighways,
|
||||
onChanged: (val) {
|
||||
setState(() => _avoidHighways = val);
|
||||
_saveBool('siro_pref_avoid_highways', val);
|
||||
},
|
||||
),
|
||||
|
||||
const SizedBox(height: 20),
|
||||
|
||||
// ── Section 2: العرض والنمط الليلي ──
|
||||
_buildSectionHeader(
|
||||
'العرض والنمط البصري', Icons.palette_rounded),
|
||||
const SizedBox(height: 8),
|
||||
_buildSwitchTile(
|
||||
title: 'التبديل التلقائي للنمط الليلي',
|
||||
subtitle:
|
||||
'تحويل الخريطة لنمط Night / Tactical تلقائياً بعد الغروب',
|
||||
value: _autoNightMode,
|
||||
onChanged: (val) {
|
||||
setState(() => _autoNightMode = val);
|
||||
_saveBool('siro_pref_auto_night', val);
|
||||
},
|
||||
),
|
||||
|
||||
const SizedBox(height: 20),
|
||||
|
||||
// ── Section 3: الذاكرة والتخزين المؤقت ──
|
||||
_buildSectionHeader(
|
||||
'الذاكرة وحزم الخرائط', Icons.storage_rounded),
|
||||
const SizedBox(height: 8),
|
||||
Container(
|
||||
padding: const EdgeInsets.all(16),
|
||||
decoration: BoxDecoration(
|
||||
color: AppColors.surfaceMuted,
|
||||
borderRadius: BorderRadius.circular(16),
|
||||
border: Border.all(color: AppColors.borderSubtle),
|
||||
),
|
||||
child: Row(
|
||||
mainAxisAlignment: MainAxisAlignment.spaceBetween,
|
||||
children: [
|
||||
Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
const Text(
|
||||
'حجم الذاكرة المؤقتة (Tiles Cache)',
|
||||
style: TextStyle(
|
||||
fontSize: 13,
|
||||
fontWeight: FontWeight.w700,
|
||||
color: AppColors.textPrimary,
|
||||
),
|
||||
),
|
||||
const SizedBox(height: 3),
|
||||
Text(
|
||||
_cacheSize,
|
||||
style: const TextStyle(
|
||||
fontSize: 12,
|
||||
fontWeight: FontWeight.w600,
|
||||
color: AppColors.appleBlue,
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
TextButton.icon(
|
||||
onPressed: _clearCache,
|
||||
icon: const Icon(Icons.delete_sweep_rounded,
|
||||
size: 18, color: AppColors.coralDanger),
|
||||
label: const Text(
|
||||
'مسح الذاكرة',
|
||||
style: TextStyle(
|
||||
fontSize: 12,
|
||||
fontWeight: FontWeight.w700,
|
||||
color: AppColors.coralDanger,
|
||||
),
|
||||
),
|
||||
style: TextButton.styleFrom(
|
||||
backgroundColor:
|
||||
AppColors.coralDanger.withValues(alpha: 0.1),
|
||||
shape: RoundedRectangleBorder(
|
||||
borderRadius: BorderRadius.circular(10)),
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
Widget _buildSectionHeader(String title, IconData icon) {
|
||||
return Row(
|
||||
children: [
|
||||
Icon(icon, size: 16, color: AppColors.appleBlue),
|
||||
const SizedBox(width: 8),
|
||||
Text(
|
||||
title,
|
||||
style: const TextStyle(
|
||||
fontSize: 13,
|
||||
fontWeight: FontWeight.w800,
|
||||
color: AppColors.textPrimary,
|
||||
fontFamily: '.SF Pro Text',
|
||||
),
|
||||
),
|
||||
],
|
||||
);
|
||||
}
|
||||
|
||||
Widget _buildSwitchTile({
|
||||
required String title,
|
||||
required String subtitle,
|
||||
required bool value,
|
||||
required ValueChanged<bool> onChanged,
|
||||
}) {
|
||||
return Container(
|
||||
margin: const EdgeInsets.only(bottom: 10),
|
||||
padding: const EdgeInsets.symmetric(horizontal: 14, vertical: 8),
|
||||
decoration: BoxDecoration(
|
||||
color: AppColors.surfaceMuted,
|
||||
borderRadius: BorderRadius.circular(16),
|
||||
border: Border.all(color: AppColors.borderSubtle),
|
||||
),
|
||||
child: Row(
|
||||
children: [
|
||||
Expanded(
|
||||
child: Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
Text(
|
||||
title,
|
||||
style: const TextStyle(
|
||||
fontSize: 13,
|
||||
fontWeight: FontWeight.w700,
|
||||
color: AppColors.textPrimary,
|
||||
),
|
||||
),
|
||||
const SizedBox(height: 2),
|
||||
Text(
|
||||
subtitle,
|
||||
style: const TextStyle(
|
||||
fontSize: 11,
|
||||
height: 1.3,
|
||||
color: AppColors.textSecondary,
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
Switch.adaptive(
|
||||
value: value,
|
||||
activeColor: AppColors.appleBlue,
|
||||
onChanged: (v) {
|
||||
HapticFeedback.lightImpact();
|
||||
onChanged(v);
|
||||
},
|
||||
),
|
||||
],
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user