diff --git a/lib/widgets/map/HMSMap.dart b/lib/widgets/map/HMSMap.dart index 96b2facd..f161cc98 100644 --- a/lib/widgets/map/HMSMap.dart +++ b/lib/widgets/map/HMSMap.dart @@ -4,60 +4,263 @@ 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/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; + + static const List<_MapTypeOption> _mapTypeOptions = [ + _MapTypeOption(type: MapType.normal, label: 'Default', icon: Icons.map_outlined), + _MapTypeOption(type: MapType.terrain, label: 'Terrain', 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 ─────────────────────────────────── + Positioned( + top: 16.h, + right: 16.w, + child: Column( + crossAxisAlignment: CrossAxisAlignment.end, + children: [ + _MapTypeToggleButton( + isActive: _showMapTypeSelector, + onTap: _toggleSelector, ), - Visibility( - visible: widget.showCenterMarker, - child: Align( - alignment: Alignment.center, - child: Icon(Icons.location_pin, size: 36.h, color: AppColors.primaryRedColor).paddingOnly(bottom: 24.h), - ), - ) + 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 : Colors.white, + borderRadius: BorderRadius.circular(8.r), + boxShadow: const [ + BoxShadow(color: Colors.black26, blurRadius: 6, offset: Offset(0, 2)), + ], + ), + child: Icon( + Icons.layers, + size: 22.h, + color: isActive ? Colors.white : Colors.black87, + ), + ), + ); + } +} + +// ── 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: Colors.white, + borderRadius: BorderRadius.circular(12.r), + boxShadow: const [ + BoxShadow(color: Colors.black26, blurRadius: 8, offset: Offset(0, 3)), ], - ); + ), + 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: 14.f, + fontWeight: isSelected ? FontWeight.w600 : FontWeight.w400, + color: isSelected ? AppColors.primaryRedColor : Colors.black87, + ), + ), + ], + ), + ), + ); + } +} + +// ── 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..f0b047ef 100644 --- a/lib/widgets/map/gms_map.dart +++ b/lib/widgets/map/gms_map.dart @@ -1,3 +1,58 @@ +// 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:flutter/material.dart'; @@ -6,49 +61,298 @@ 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; +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 + static const List<_MapTypeOption> _mapTypeOptions = [ + _MapTypeOption( + type: MapType.normal, + label: 'Default', + icon: Icons.map_outlined, + ), + _MapTypeOption( + type: MapType.satellite, + label: 'Satellite', + icon: Icons.satellite_alt_outlined, + ), + _MapTypeOption( + type: MapType.terrain, + label: 'Terrain', + icon: Icons.terrain_outlined, + ), + _MapTypeOption( + type: MapType.hybrid, + label: 'Hybrid', + icon: Icons.layers_outlined, + ), + ]; + @override + void initState() { + super.initState(); + _controller = widget.inputController ?? Completer(); + _selectedMapType = widget.initialMapType; + } + + void _onMapTypeSelected(MapType type) { + setState(() { + _selectedMapType = type; + _showMapTypeSelector = false; + }); + } - 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(); + 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 ────────────────────────────────────── + Positioned( + top: 48.h, + left: 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 : Colors.white, + borderRadius: BorderRadius.circular(8.r), + boxShadow: const [ + BoxShadow( + color: Colors.black26, + blurRadius: 6, + offset: Offset(0, 2), + ), + ], + ), + child: Icon( + Icons.layers, + size: 22.h, + color: isActive ? Colors.white : Colors.black87, + ), + ), + ); + } +} + +// ── 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: Colors.white, + borderRadius: BorderRadius.circular(12.r), + boxShadow: const [ + BoxShadow( + color: Colors.black26, + blurRadius: 8, + offset: Offset(0, 3), + ), + ], + ), + 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 : Colors.black87, + ), + ), + ], + ), + ), + ); + } +} + +// ── 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