diff --git a/assets/langs/ar-SA.json b/assets/langs/ar-SA.json index f9cc3035..cd92703c 100644 --- a/assets/langs/ar-SA.json +++ b/assets/langs/ar-SA.json @@ -1653,5 +1653,9 @@ "oneWaySubtitle": "نقل من الموقع إلى المستشفى", "twoWaySubtitle": "نقل من الموقع إلى المستشفى والعودة مرة أخرى", "toHospitalSubtitle": "نقل من موقعك الحالي إلى المستشفى", - "fromHospitalSubtitle": "نقل من المستشفى إلى منزلك" + "fromHospitalSubtitle": "نقل من المستشفى إلى منزلك", + "normalMapType": "افتراضي", + "satelliteMapType": "قمر صناعي", + "terrainMapType": "تضاريس", + "hybridMapType": "هجين" } diff --git a/assets/langs/en-US.json b/assets/langs/en-US.json index 1bbd7cb7..3d633713 100644 --- a/assets/langs/en-US.json +++ b/assets/langs/en-US.json @@ -1645,5 +1645,9 @@ "oneWaySubtitle": "Pickup from location to hospital", "twoWaySubtitle": "Round trip from location to hospital and back", "toHospitalSubtitle": "Transfer from your current location to the hospital", - "fromHospitalSubtitle": "Transfer from the hospital back to your home" + "fromHospitalSubtitle": "Transfer from the hospital back to your home", + "normalMapType": "Default", + "satelliteMapType": "Satellite", + "terrainMapType": "Terrain", + "hybridMapType": "Hybrid" } diff --git a/lib/extensions/string_extensions.dart b/lib/extensions/string_extensions.dart index a68bdcf1..4ea8a17d 100644 --- a/lib/extensions/string_extensions.dart +++ b/lib/extensions/string_extensions.dart @@ -114,7 +114,8 @@ extension EmailValidator on String { bool isCenter = false, double? height, double? letterSpacing, - int maxLine = 0}) => + int maxLine = 0, String? fontFamily, + }) => Text( this, textAlign: isCenter ? TextAlign.center : textAlignment, @@ -127,7 +128,7 @@ extension EmailValidator on String { height: height, decorationColor: isUnderLine ? AppColors.blackColor : null, decoration: isUnderLine ? TextDecoration.underline : null, - fontFamily: isEnglishOnly ? "Poppins" : getIt.get().getLanguageCode() == "ar" ? 'GESSTwo' : 'Poppins', + fontFamily:fontFamily ??( isEnglishOnly ? "Poppins" : getIt.get().getLanguageCode() == "ar" ? 'GESSTwo' : 'Poppins'), ), ); @@ -284,10 +285,11 @@ extension EmailValidator on String { style: TextStyle(fontSize: 20.f, fontWeight: weight ?? (isBold ? FontWeight.bold : FontWeight.normal), color: color ?? AppColors.blackColor, letterSpacing: -0.4), ); - Widget toText21({Color? color, bool isBold = false, FontWeight? weight, int? maxlines}) => Text( + Widget toText21({Color? color, bool isBold = false, FontWeight? weight, int? maxlines, bool isEnglishOnly = false}) => Text( this, maxLines: maxlines, - style: TextStyle(color: color ?? AppColors.blackColor, fontSize: 21.f, letterSpacing: -1, fontWeight: weight ?? (isBold ? FontWeight.bold : FontWeight.normal)), + style: TextStyle(color: color ?? AppColors.blackColor, fontSize: 21.f, letterSpacing: -1, fontWeight: weight ?? (isBold ? FontWeight.bold : FontWeight.normal), + fontFamily: (isEnglishOnly ? "Poppins" : getIt.get().getLanguageCode() == "ar" ? 'GESSTwo' : 'Poppins')), ); Widget toText22({Color? color, bool isBold = false, bool isCenter = false}) => Text( diff --git a/lib/features/my_appointments/models/resp_models/hospital_model.dart b/lib/features/my_appointments/models/resp_models/hospital_model.dart index a807b99a..031b3fd4 100644 --- a/lib/features/my_appointments/models/resp_models/hospital_model.dart +++ b/lib/features/my_appointments/models/resp_models/hospital_model.dart @@ -75,9 +75,7 @@ class HospitalsModel { } String? getName(bool isArabic) { - if (isArabic) { - return "$nameN"; - } + return name; } diff --git a/lib/generated/locale_keys.g.dart b/lib/generated/locale_keys.g.dart index e9f60f5c..7a659cc2 100644 --- a/lib/generated/locale_keys.g.dart +++ b/lib/generated/locale_keys.g.dart @@ -1646,5 +1646,9 @@ abstract class LocaleKeys { static const twoWaySubtitle = 'twoWaySubtitle'; static const toHospitalSubtitle = 'toHospitalSubtitle'; static const fromHospitalSubtitle = 'fromHospitalSubtitle'; + static const normalMapType = 'normalMapType'; + static const satelliteMapType = 'satelliteMapType'; + static const terrainMapType = 'terrainMapType'; + static const hybridMapType = 'hybridMapType'; } diff --git a/lib/presentation/emergency_services/call_ambulance/call_ambulance_page.dart b/lib/presentation/emergency_services/call_ambulance/call_ambulance_page.dart index e5578a3e..2512ad6b 100644 --- a/lib/presentation/emergency_services/call_ambulance/call_ambulance_page.dart +++ b/lib/presentation/emergency_services/call_ambulance/call_ambulance_page.dart @@ -159,10 +159,12 @@ class CallAmbulancePage extends StatelessWidget { LocaleKeys.selectPickupDetails.tr(context: context).toText21( weight: FontWeight.w600, color: AppColors.textColor, + isEnglishOnly: true, ), LocaleKeys.pleaseSelectDetailsOfPickup.tr(context: context).toText12( fontWeight: FontWeight.w500, color: AppColors.greyTextColor, + isEnglishOnly: true, ) ], ), @@ -385,6 +387,7 @@ class CallAmbulancePage extends StatelessWidget { title.toText12( color: AppColors.greyTextColor, fontWeight: FontWeight.w500, + isEnglishOnly: true ), SizedBox(height: 2.h), ], @@ -393,6 +396,7 @@ class CallAmbulancePage extends StatelessWidget { subTitle.toText14( color: AppColors.textColor, weight: FontWeight.w500, + isEnglishOnly: true ), ], ), @@ -527,7 +531,7 @@ class CallAmbulancePage extends StatelessWidget { textPlaceInput(context) { return Consumer(builder: (_, vm, __) { // print("the data is ${vm.geocodeResponse?.results.first.formattedAddress ?? vm.selectedPrediction?.description}"); - return (vm.geocodeResponse != null && vm.geocodeResponse!.results.isNotEmpty) ? SizedBox( + return SizedBox( width: MediaQuery.sizeOf(context).width, child: TextInputWidget( labelText: LocaleKeys.enterPickupLocationManually.tr(context: context), @@ -546,10 +550,11 @@ class CallAmbulancePage extends StatelessWidget { vertical: ResponsiveExtension(10).h, horizontal: ResponsiveExtension(15).h, ), + fontFamily: "Poppins", ).onPress(() { openLocationInputBottomSheet(context); }), - ) : SizedBox.shrink(); + ); }); } @@ -561,7 +566,7 @@ class CallAmbulancePage extends StatelessWidget { } openLocationInputBottomSheet(BuildContext context) { - context.read().flushSearchPredictions(); + // context.read().flushSearchPredictions(); showCommonBottomSheetWithoutHeight( title: "", context, diff --git a/lib/presentation/emergency_services/widgets/location_input_bottom_sheet.dart b/lib/presentation/emergency_services/widgets/location_input_bottom_sheet.dart index db45654a..938d520c 100644 --- a/lib/presentation/emergency_services/widgets/location_input_bottom_sheet.dart +++ b/lib/presentation/emergency_services/widgets/location_input_bottom_sheet.dart @@ -4,6 +4,8 @@ import 'package:flutter_staggered_animations/flutter_staggered_animations.dart' show AnimationConfiguration, SlideAnimation, FadeInAnimation; import 'package:hmg_patient_app_new/core/app_assets.dart'; import 'package:hmg_patient_app_new/core/app_export.dart'; +import 'package:hmg_patient_app_new/core/app_state.dart'; +import 'package:hmg_patient_app_new/core/dependencies.dart'; import 'package:hmg_patient_app_new/core/enums.dart'; import 'package:hmg_patient_app_new/core/utils/debouncer.dart'; import 'package:hmg_patient_app_new/core/utils/utils.dart'; @@ -49,6 +51,7 @@ class LocationInputBottomSheet extends StatelessWidget { vertical: 10.h, horizontal: 15.h, ), + fontFamily: "Poppins", ), SizedBox(height: 24.h), Selector>( @@ -82,16 +85,20 @@ class LocationInputBottomSheet extends StatelessWidget { Expanded( child: prediction.description.toText14( color: AppColors.textColor, - weight: FontWeight.w500), + weight: FontWeight.w500, + isEnglishOnly: true, + ), ), - - Utils.buildSvgWithAssets( - icon: AppAssets.forward_arrow_icon_small, + Transform.flip( + flipX: getIt.get().isArabic(), + child: Utils.buildSvgWithAssets( + icon: AppAssets.forward_arrow_icon_small, iconColor: AppColors.blackColor, width: 20.h, height: 15.h, fit: BoxFit.contain, - ), + ), + ) ], ).paddingAll(24.h),), ), diff --git a/lib/widgets/input_widget.dart b/lib/widgets/input_widget.dart index a64775aa..b3fbc02b 100644 --- a/lib/widgets/input_widget.dart +++ b/lib/widgets/input_widget.dart @@ -290,7 +290,7 @@ class TextInputWidget extends StatelessWidget { } Widget _buildLabelText(Color? labelColor) { - return labelText.toText12(fontWeight: FontWeight.w500, color: labelColor ?? AppColors.inputLabelTextColor); + return labelText.toText12(fontWeight: FontWeight.w500, color: labelColor ?? AppColors.inputLabelTextColor, fontFamily: fontFamily); } Widget _buildTextField(BuildContext context) { diff --git a/lib/widgets/map/HMSMap.dart b/lib/widgets/map/HMSMap.dart index 96b2facd..50da5f3a 100644 --- a/lib/widgets/map/HMSMap.dart +++ b/lib/widgets/map/HMSMap.dart @@ -1,63 +1,263 @@ import 'dart:async'; +import 'package:easy_localization/easy_localization.dart'; import 'package:flutter/material.dart'; import 'package:hmg_patient_app_new/core/app_export.dart'; import 'package:hmg_patient_app_new/extensions/widget_extensions.dart'; +import 'package:hmg_patient_app_new/generated/locale_keys.g.dart'; import 'package:hmg_patient_app_new/theme/colors.dart'; -import 'package:huawei_map/huawei_map.dart' ; +import 'package:huawei_map/huawei_map.dart'; -class HMSMap extends StatefulWidget{ +class HMSMap extends StatefulWidget { final CameraPosition currentLocation; final Function(CameraPosition) onCameraMoved; final VoidCallback onCameraIdle; - final MapType mapType; + final MapType initialMapType; final bool compassEnabled; final bool myLocationEnabled; final bool showCenterMarker; final Completer? inputController; - HMSMap({super.key, required this.currentLocation ,required this.onCameraMoved,required this.onCameraIdle, - this.mapType = MapType.normal,this.compassEnabled = false,this.showCenterMarker = false, - this.myLocationEnabled = true, this.inputController}); + const HMSMap({ + super.key, + required this.currentLocation, + required this.onCameraMoved, + required this.onCameraIdle, + this.initialMapType = MapType.normal, + this.compassEnabled = false, + this.showCenterMarker = false, + this.myLocationEnabled = true, + this.inputController, + }); @override State createState() => _HMSMapState(); } class _HMSMapState extends State { - Completer? controller; + Completer? controller; + late MapType _selectedMapType; + bool _showMapTypeSelector = false; + + final List<_MapTypeOption> _mapTypeOptions = [ + _MapTypeOption(type: MapType.normal, label: LocaleKeys.normalMapType.tr(), icon: Icons.map_outlined), + _MapTypeOption(type: MapType.terrain, label: LocaleKeys.terrainMapType.tr(), icon: Icons.terrain_outlined), + ]; @override void initState() { + super.initState(); HuaweiMapInitializer.initializeMap(); controller = widget.inputController ?? Completer(); - super.initState(); + _selectedMapType = widget.initialMapType; + } + + void _onMapTypeSelected(MapType type) { + setState(() { + _selectedMapType = type; + _showMapTypeSelector = false; + }); } - // @override + + void _toggleSelector() => + setState(() => _showMapTypeSelector = !_showMapTypeSelector); + @override - Widget build(BuildContext context) => - Stack( - children: [ - HuaweiMap( - mapType: widget.mapType, - zoomControlsEnabled: false, - myLocationEnabled: widget.myLocationEnabled, - myLocationButtonEnabled: false, - compassEnabled: widget.compassEnabled, - onCameraIdle:()=> widget.onCameraIdle(), - initialCameraPosition: widget.currentLocation, - onCameraMove: (value) => widget.onCameraMoved(value), - onMapCreated: (HuaweiMapController controller) { - this.controller?.complete(controller); - }, + Widget build(BuildContext context) { + return Stack( + children: [ + // ── Huawei Map ──────────────────────────────────────────────── + HuaweiMap( + mapType: _selectedMapType, + zoomControlsEnabled: false, + myLocationEnabled: widget.myLocationEnabled, + myLocationButtonEnabled: false, + compassEnabled: widget.compassEnabled, + onCameraIdle: widget.onCameraIdle, + initialCameraPosition: widget.currentLocation, + onCameraMove: widget.onCameraMoved, + onMapCreated: (HuaweiMapController ctrl) { + controller?.complete(ctrl); + }, + ), + + // ── Centre pin marker ───────────────────────────────────────── + if (widget.showCenterMarker) + Align( + alignment: Alignment.center, + child: Icon( + Icons.location_pin, + size: 36.h, + color: AppColors.primaryRedColor, + ).paddingOnly(bottom: 24.h), + ), + + // ── Map-type toggle + panel ─────────────────────────────────── + PositionedDirectional( + top: 48.h, + end: 16.w, + child: Column( + crossAxisAlignment: CrossAxisAlignment.end, + children: [ + _MapTypeToggleButton( + isActive: _showMapTypeSelector, + onTap: _toggleSelector, + ), + AnimatedSwitcher( + duration: const Duration(milliseconds: 220), + transitionBuilder: (child, animation) => FadeTransition( + opacity: animation, + child: SizeTransition( + sizeFactor: animation, + axisAlignment: -1, + child: child, + ), + ), + child: _showMapTypeSelector + ? _MapTypeSelectorPanel( + options: _mapTypeOptions, + selected: _selectedMapType, + onSelected: _onMapTypeSelected, + ) + : const SizedBox.shrink(), ), - Visibility( - visible: widget.showCenterMarker, - child: Align( - alignment: Alignment.center, - child: Icon(Icons.location_pin, size: 36.h, color: AppColors.primaryRedColor).paddingOnly(bottom: 24.h), + ], + ), + ), + ], + ); + } +} + +// ── Toggle button ───────────────────────────────────────────────────────────── + +class _MapTypeToggleButton extends StatelessWidget { + final bool isActive; + final VoidCallback onTap; + + const _MapTypeToggleButton({required this.isActive, required this.onTap}); + + @override + Widget build(BuildContext context) { + return GestureDetector( + onTap: onTap, + child: AnimatedContainer( + duration: const Duration(milliseconds: 200), + width: 42.w, + height: 42.h, + decoration: BoxDecoration( + color: isActive ? AppColors.primaryRedColor : AppColors.whiteColor, + borderRadius: BorderRadius.circular(8.r), + ), + child: Icon( + Icons.layers, + size: 22.h, + color: isActive ? AppColors.whiteColor : AppColors.blackBgColor, + ), + ), + ); + } +} + +// ── Selector panel ──────────────────────────────────────────────────────────── + +class _MapTypeSelectorPanel extends StatelessWidget { + final List<_MapTypeOption> options; + final MapType selected; + final ValueChanged onSelected; + + const _MapTypeSelectorPanel({ + required this.options, + required this.selected, + required this.onSelected, + }); + + @override + Widget build(BuildContext context) { + return Container( + key: const ValueKey('map_type_panel'), + margin: EdgeInsets.only(top: 8.h), + padding: EdgeInsets.symmetric(vertical: 8.h, horizontal: 6.w), + decoration: BoxDecoration( + color: AppColors.whiteColor, + borderRadius: BorderRadius.circular(12.r), + + ), + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: options + .map((opt) => _MapTypeItem( + option: opt, + isSelected: selected == opt.type, + onTap: () => onSelected(opt.type), + )) + .toList(), + ), + ); + } +} + +// ── Single row item ─────────────────────────────────────────────────────────── + +class _MapTypeItem extends StatelessWidget { + final _MapTypeOption option; + final bool isSelected; + final VoidCallback onTap; + + const _MapTypeItem({ + required this.option, + required this.isSelected, + required this.onTap, + }); + + @override + Widget build(BuildContext context) { + return GestureDetector( + onTap: onTap, + child: AnimatedContainer( + duration: const Duration(milliseconds: 180), + margin: EdgeInsets.symmetric(vertical: 3.h), + padding: EdgeInsets.symmetric(horizontal: 10.w, vertical: 8.h), + decoration: BoxDecoration( + color: isSelected + ? AppColors.primaryRedColor.withOpacity(0.1) + : Colors.transparent, + borderRadius: BorderRadius.circular(8.r), + ), + child: Row( + mainAxisSize: MainAxisSize.min, + children: [ + Icon( + option.icon, + size: 18.h, + color: isSelected ? AppColors.primaryRedColor : AppColors.blackBgColor, ), - ) - ], - ); + SizedBox(width: 8.w), + Text( + option.label, + style: TextStyle( + fontSize: 14.f, + fontWeight: isSelected ? FontWeight.w600 : FontWeight.w400, + color: isSelected ? AppColors.primaryRedColor :AppColors.blackBgColor, + ), + ), + ], + ), + ), + ); + } +} + +// ── Data class ──────────────────────────────────────────────────────────────── + +class _MapTypeOption { + final MapType type; + final String label; + final IconData icon; + + const _MapTypeOption({ + required this.type, + required this.label, + required this.icon, + }); } \ No newline at end of file diff --git a/lib/widgets/map/gms_map.dart b/lib/widgets/map/gms_map.dart index f4c3ad54..7ffe6ac6 100644 --- a/lib/widgets/map/gms_map.dart +++ b/lib/widgets/map/gms_map.dart @@ -1,54 +1,346 @@ +// import 'dart:async'; +// +// import 'package:flutter/material.dart'; +// import 'package:google_maps_flutter/google_maps_flutter.dart'; +// import 'package:hmg_patient_app_new/core/utils/size_utils.dart'; +// import 'package:hmg_patient_app_new/extensions/widget_extensions.dart'; +// import 'package:hmg_patient_app_new/theme/colors.dart'; +// +// class GMSMap extends StatelessWidget{ +// Completer? controller; +// final CameraPosition currentLocation; +// final Function(CameraPosition) onCameraMoved; +// final VoidCallback onCameraIdle; +// final MapType mapType; +// final bool compassEnabled; +// final bool myLocationEnabled; +// final bool showCenterMarker; +// +// +// GMSMap({super.key, required this.currentLocation ,required this.onCameraMoved,required this.onCameraIdle, +// this.mapType = MapType.normal,this.compassEnabled = false,this.showCenterMarker = false, +// this.myLocationEnabled = true,Completer? inputController}){ +// controller = inputController ?? Completer(); +// } +// +// @override +// Widget build(BuildContext context) { +// return Stack( +// children: [ +// GoogleMap( +// mapType: mapType, +// zoomControlsEnabled: true, +// myLocationEnabled: myLocationEnabled, +// myLocationButtonEnabled: false, +// compassEnabled: compassEnabled, +// initialCameraPosition: currentLocation, +// onCameraMove: (value) => onCameraMoved(value), +// onCameraIdle: ()=>onCameraIdle(), +// // padding: EdgeInsets.only(bottom: 300.h), +// onMapCreated: (GoogleMapController controller) { +// this.controller?.complete(controller); +// }, +// ), +// Visibility( +// visible: showCenterMarker, +// child: Align( +// alignment: Alignment.center, +// child: Icon(Icons.location_pin, size: 36.h, color: AppColors.primaryRedColor).paddingOnly(bottom: 24.h), +// ), +// ) +// ], +// ); +// } +// } + import 'dart:async'; +import 'package:easy_localization/easy_localization.dart'; import 'package:flutter/material.dart'; import 'package:google_maps_flutter/google_maps_flutter.dart'; import 'package:hmg_patient_app_new/core/utils/size_utils.dart'; import 'package:hmg_patient_app_new/extensions/widget_extensions.dart'; +import 'package:hmg_patient_app_new/generated/locale_keys.g.dart'; import 'package:hmg_patient_app_new/theme/colors.dart'; -class GMSMap extends StatelessWidget{ - Completer? controller; +class GMSMap extends StatefulWidget { final CameraPosition currentLocation; final Function(CameraPosition) onCameraMoved; final VoidCallback onCameraIdle; - final MapType mapType; + final MapType initialMapType; final bool compassEnabled; final bool myLocationEnabled; final bool showCenterMarker; + final Completer? inputController; + + const GMSMap({ + super.key, + required this.currentLocation, + required this.onCameraMoved, + required this.onCameraIdle, + this.initialMapType = MapType.normal, + this.compassEnabled = false, + this.showCenterMarker = false, + this.myLocationEnabled = true, + this.inputController, + }); + + @override + State createState() => _GMSMapState(); +} + +class _GMSMapState extends State { + late Completer _controller; + late MapType _selectedMapType; + bool _showMapTypeSelector = false; + /// All supported map types with display labels and icons + final List<_MapTypeOption> _mapTypeOptions = [ + _MapTypeOption( + type: MapType.normal, + label: LocaleKeys.normalMapType.tr(), + icon: Icons.map_outlined, + ), + _MapTypeOption( + type: MapType.satellite, + label: LocaleKeys.satelliteMapType.tr(), + icon: Icons.satellite_alt_outlined, + ), + _MapTypeOption( + type: MapType.terrain, + label: LocaleKeys.terrainMapType.tr(), + icon: Icons.terrain_outlined, + ), + _MapTypeOption( + type: MapType.hybrid, + label: LocaleKeys.hybridMapType.tr(), + icon: Icons.layers_outlined, + ), + ]; - GMSMap({super.key, required this.currentLocation ,required this.onCameraMoved,required this.onCameraIdle, - this.mapType = MapType.normal,this.compassEnabled = false,this.showCenterMarker = false, - this.myLocationEnabled = true,Completer? inputController}){ - controller = inputController ?? Completer(); + @override + void initState() { + super.initState(); + _controller = widget.inputController ?? Completer(); + _selectedMapType = widget.initialMapType; + } + + void _onMapTypeSelected(MapType type) { + setState(() { + _selectedMapType = type; + _showMapTypeSelector = false; + }); + } + + void _toggleMapTypeSelector() { + setState(() => _showMapTypeSelector = !_showMapTypeSelector); } @override Widget build(BuildContext context) { - return Stack( - children: [ - GoogleMap( - mapType: mapType, + return Stack( + children: [ + // ── Google Map ────────────────────────────────────────────────── + GoogleMap( + mapType: _selectedMapType, zoomControlsEnabled: true, - myLocationEnabled: myLocationEnabled, + myLocationEnabled: widget.myLocationEnabled, myLocationButtonEnabled: false, - compassEnabled: compassEnabled, - initialCameraPosition: currentLocation, - onCameraMove: (value) => onCameraMoved(value), - onCameraIdle: ()=>onCameraIdle(), - // padding: EdgeInsets.only(bottom: 300.h), + compassEnabled: widget.compassEnabled, + initialCameraPosition: widget.currentLocation, + onCameraMove: widget.onCameraMoved, + onCameraIdle: widget.onCameraIdle, onMapCreated: (GoogleMapController controller) { - this.controller?.complete(controller); + _controller.complete(controller); }, - ), - Visibility( - visible: showCenterMarker, - child: Align( - alignment: Alignment.center, - child: Icon(Icons.location_pin, size: 36.h, color: AppColors.primaryRedColor).paddingOnly(bottom: 24.h), + ), + + // ── Centre pin marker ─────────────────────────────────────────── + if (widget.showCenterMarker) + Align( + alignment: Alignment.center, + child: Icon( + Icons.location_pin, + size: 36.h, + color: AppColors.primaryRedColor, + ).paddingOnly(bottom: 24.h), ), - ) + + // ── Map-type toggle button ────────────────────────────────────── + PositionedDirectional( + top: 48.h, + end: 16.w, + child: Column( + crossAxisAlignment: CrossAxisAlignment.end, + children: [ + // Toggle button + _MapTypeToggleButton( + isActive: _showMapTypeSelector, + onTap: _toggleMapTypeSelector, + ), + + // Expandable selector panel + AnimatedSwitcher( + duration: const Duration(milliseconds: 220), + transitionBuilder: (child, animation) => FadeTransition( + opacity: animation, + child: SizeTransition( + sizeFactor: animation, + axisAlignment: -1, + child: child, + ), + ), + child: _showMapTypeSelector + ? _MapTypeSelectorPanel( + options: _mapTypeOptions, + selected: _selectedMapType, + onSelected: _onMapTypeSelected, + ) + : const SizedBox.shrink(), + ), + ], + ), + ), ], ); } +} + +// ── Toggle button ───────────────────────────────────────────────────────────── + +class _MapTypeToggleButton extends StatelessWidget { + final bool isActive; + final VoidCallback onTap; + + const _MapTypeToggleButton({ + required this.isActive, + required this.onTap, + }); + + @override + Widget build(BuildContext context) { + return GestureDetector( + onTap: onTap, + child: AnimatedContainer( + duration: const Duration(milliseconds: 200), + width: 42.w, + height: 42.h, + decoration: BoxDecoration( + color: isActive ? AppColors.primaryRedColor : AppColors.whiteColor, + borderRadius: BorderRadius.circular(8.r), + ), + child: Icon( + Icons.layers, + size: 22.h, + color: isActive ? AppColors.whiteColor : AppColors.blackBgColor, + ), + ), + ); + } +} + +// ── Selector panel ──────────────────────────────────────────────────────────── + +class _MapTypeSelectorPanel extends StatelessWidget { + final List<_MapTypeOption> options; + final MapType selected; + final ValueChanged onSelected; + + const _MapTypeSelectorPanel({ + required this.options, + required this.selected, + required this.onSelected, + }); + + @override + Widget build(BuildContext context) { + return Container( + key: const ValueKey('map_type_panel'), + margin: EdgeInsets.only(top: 8.h), + padding: EdgeInsets.symmetric(vertical: 8.h, horizontal: 6.w), + decoration: BoxDecoration( + color: AppColors.whiteColor, + borderRadius: BorderRadius.circular(12.r), + ), + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: options + .map( + (opt) => _MapTypeItem( + option: opt, + isSelected: selected == opt.type, + onTap: () => onSelected(opt.type), + ), + ) + .toList(), + ), + ); + } +} + +// ── Single row item ─────────────────────────────────────────────────────────── + +class _MapTypeItem extends StatelessWidget { + final _MapTypeOption option; + final bool isSelected; + final VoidCallback onTap; + + const _MapTypeItem({ + required this.option, + required this.isSelected, + required this.onTap, + }); + + @override + Widget build(BuildContext context) { + return GestureDetector( + onTap: onTap, + child: AnimatedContainer( + duration: const Duration(milliseconds: 180), + margin: EdgeInsets.symmetric(vertical: 3.h), + padding: EdgeInsets.symmetric(horizontal: 10.w, vertical: 8.h), + decoration: BoxDecoration( + color: isSelected + ? AppColors.primaryRedColor.withOpacity(0.1) + : Colors.transparent, + borderRadius: BorderRadius.circular(8.r), + ), + child: Row( + mainAxisSize: MainAxisSize.min, + children: [ + Icon( + option.icon, + size: 18.h, + color: isSelected ? AppColors.primaryRedColor : Colors.black54, + ), + SizedBox(width: 8.w), + Text( + option.label, + style: TextStyle( + fontSize: 13.f, + fontWeight: + isSelected ? FontWeight.w600 : FontWeight.w400, + color: + isSelected ? AppColors.primaryRedColor : AppColors.blackBgColor, + ), + ), + ], + ), + ), + ); + } +} + +// ── Data class ──────────────────────────────────────────────────────────────── + +class _MapTypeOption { + final MapType type; + final String label; + final IconData icon; + + const _MapTypeOption({ + required this.type, + required this.label, + required this.icon, + }); } \ No newline at end of file