Merge pull request 'Callambulance page text and map changes' (#211) from maptype into master

Reviewed-on: https://34.17.182.140/Haroon6138/HMG_Patient_App_New/pulls/211
pull/214/head
Haroon6138 1 day ago
commit 59fc109454

@ -1653,5 +1653,9 @@
"oneWaySubtitle": "نقل من الموقع إلى المستشفى", "oneWaySubtitle": "نقل من الموقع إلى المستشفى",
"twoWaySubtitle": "نقل من الموقع إلى المستشفى والعودة مرة أخرى", "twoWaySubtitle": "نقل من الموقع إلى المستشفى والعودة مرة أخرى",
"toHospitalSubtitle": "نقل من موقعك الحالي إلى المستشفى", "toHospitalSubtitle": "نقل من موقعك الحالي إلى المستشفى",
"fromHospitalSubtitle": "نقل من المستشفى إلى منزلك" "fromHospitalSubtitle": "نقل من المستشفى إلى منزلك",
"normalMapType": "افتراضي",
"satelliteMapType": "قمر صناعي",
"terrainMapType": "تضاريس",
"hybridMapType": "هجين"
} }

@ -1645,5 +1645,9 @@
"oneWaySubtitle": "Pickup from location to hospital", "oneWaySubtitle": "Pickup from location to hospital",
"twoWaySubtitle": "Round trip from location to hospital and back", "twoWaySubtitle": "Round trip from location to hospital and back",
"toHospitalSubtitle": "Transfer from your current location to the hospital", "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"
} }

@ -114,7 +114,8 @@ extension EmailValidator on String {
bool isCenter = false, bool isCenter = false,
double? height, double? height,
double? letterSpacing, double? letterSpacing,
int maxLine = 0}) => int maxLine = 0, String? fontFamily,
}) =>
Text( Text(
this, this,
textAlign: isCenter ? TextAlign.center : textAlignment, textAlign: isCenter ? TextAlign.center : textAlignment,
@ -127,7 +128,7 @@ extension EmailValidator on String {
height: height, height: height,
decorationColor: isUnderLine ? AppColors.blackColor : null, decorationColor: isUnderLine ? AppColors.blackColor : null,
decoration: isUnderLine ? TextDecoration.underline : null, decoration: isUnderLine ? TextDecoration.underline : null,
fontFamily: isEnglishOnly ? "Poppins" : getIt.get<AppState>().getLanguageCode() == "ar" ? 'GESSTwo' : 'Poppins', fontFamily:fontFamily ??( isEnglishOnly ? "Poppins" : getIt.get<AppState>().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), 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, this,
maxLines: maxlines, 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<AppState>().getLanguageCode() == "ar" ? 'GESSTwo' : 'Poppins')),
); );
Widget toText22({Color? color, bool isBold = false, bool isCenter = false}) => Text( Widget toText22({Color? color, bool isBold = false, bool isCenter = false}) => Text(

@ -75,9 +75,7 @@ class HospitalsModel {
} }
String? getName(bool isArabic) { String? getName(bool isArabic) {
if (isArabic) {
return "$nameN";
}
return name; return name;
} }

@ -1646,5 +1646,9 @@ abstract class LocaleKeys {
static const twoWaySubtitle = 'twoWaySubtitle'; static const twoWaySubtitle = 'twoWaySubtitle';
static const toHospitalSubtitle = 'toHospitalSubtitle'; static const toHospitalSubtitle = 'toHospitalSubtitle';
static const fromHospitalSubtitle = 'fromHospitalSubtitle'; static const fromHospitalSubtitle = 'fromHospitalSubtitle';
static const normalMapType = 'normalMapType';
static const satelliteMapType = 'satelliteMapType';
static const terrainMapType = 'terrainMapType';
static const hybridMapType = 'hybridMapType';
} }

@ -159,10 +159,12 @@ class CallAmbulancePage extends StatelessWidget {
LocaleKeys.selectPickupDetails.tr(context: context).toText21( LocaleKeys.selectPickupDetails.tr(context: context).toText21(
weight: FontWeight.w600, weight: FontWeight.w600,
color: AppColors.textColor, color: AppColors.textColor,
isEnglishOnly: true,
), ),
LocaleKeys.pleaseSelectDetailsOfPickup.tr(context: context).toText12( LocaleKeys.pleaseSelectDetailsOfPickup.tr(context: context).toText12(
fontWeight: FontWeight.w500, fontWeight: FontWeight.w500,
color: AppColors.greyTextColor, color: AppColors.greyTextColor,
isEnglishOnly: true,
) )
], ],
), ),
@ -385,6 +387,7 @@ class CallAmbulancePage extends StatelessWidget {
title.toText12( title.toText12(
color: AppColors.greyTextColor, color: AppColors.greyTextColor,
fontWeight: FontWeight.w500, fontWeight: FontWeight.w500,
isEnglishOnly: true
), ),
SizedBox(height: 2.h), SizedBox(height: 2.h),
], ],
@ -393,6 +396,7 @@ class CallAmbulancePage extends StatelessWidget {
subTitle.toText14( subTitle.toText14(
color: AppColors.textColor, color: AppColors.textColor,
weight: FontWeight.w500, weight: FontWeight.w500,
isEnglishOnly: true
), ),
], ],
), ),
@ -527,7 +531,7 @@ class CallAmbulancePage extends StatelessWidget {
textPlaceInput(context) { textPlaceInput(context) {
return Consumer<LocationViewModel>(builder: (_, vm, __) { return Consumer<LocationViewModel>(builder: (_, vm, __) {
// print("the data is ${vm.geocodeResponse?.results.first.formattedAddress ?? vm.selectedPrediction?.description}"); // 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, width: MediaQuery.sizeOf(context).width,
child: TextInputWidget( child: TextInputWidget(
labelText: LocaleKeys.enterPickupLocationManually.tr(context: context), labelText: LocaleKeys.enterPickupLocationManually.tr(context: context),
@ -546,10 +550,11 @@ class CallAmbulancePage extends StatelessWidget {
vertical: ResponsiveExtension(10).h, vertical: ResponsiveExtension(10).h,
horizontal: ResponsiveExtension(15).h, horizontal: ResponsiveExtension(15).h,
), ),
fontFamily: "Poppins",
).onPress(() { ).onPress(() {
openLocationInputBottomSheet(context); openLocationInputBottomSheet(context);
}), }),
) : SizedBox.shrink(); );
}); });
} }
@ -561,7 +566,7 @@ class CallAmbulancePage extends StatelessWidget {
} }
openLocationInputBottomSheet(BuildContext context) { openLocationInputBottomSheet(BuildContext context) {
context.read<LocationViewModel>().flushSearchPredictions(); // context.read<LocationViewModel>().flushSearchPredictions();
showCommonBottomSheetWithoutHeight( showCommonBottomSheetWithoutHeight(
title: "", title: "",
context, context,

@ -4,6 +4,8 @@ import 'package:flutter_staggered_animations/flutter_staggered_animations.dart'
show AnimationConfiguration, SlideAnimation, FadeInAnimation; show AnimationConfiguration, SlideAnimation, FadeInAnimation;
import 'package:hmg_patient_app_new/core/app_assets.dart'; 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_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/enums.dart';
import 'package:hmg_patient_app_new/core/utils/debouncer.dart'; import 'package:hmg_patient_app_new/core/utils/debouncer.dart';
import 'package:hmg_patient_app_new/core/utils/utils.dart'; import 'package:hmg_patient_app_new/core/utils/utils.dart';
@ -49,6 +51,7 @@ class LocationInputBottomSheet extends StatelessWidget {
vertical: 10.h, vertical: 10.h,
horizontal: 15.h, horizontal: 15.h,
), ),
fontFamily: "Poppins",
), ),
SizedBox(height: 24.h), SizedBox(height: 24.h),
Selector<LocationViewModel, List<PlacePrediction>>( Selector<LocationViewModel, List<PlacePrediction>>(
@ -82,16 +85,20 @@ class LocationInputBottomSheet extends StatelessWidget {
Expanded( Expanded(
child: prediction.description.toText14( child: prediction.description.toText14(
color: AppColors.textColor, color: AppColors.textColor,
weight: FontWeight.w500), weight: FontWeight.w500,
isEnglishOnly: true,
),
), ),
Transform.flip(
Utils.buildSvgWithAssets( flipX: getIt.get<AppState>().isArabic(),
icon: AppAssets.forward_arrow_icon_small, child: Utils.buildSvgWithAssets(
icon: AppAssets.forward_arrow_icon_small,
iconColor: AppColors.blackColor, iconColor: AppColors.blackColor,
width: 20.h, width: 20.h,
height: 15.h, height: 15.h,
fit: BoxFit.contain, fit: BoxFit.contain,
), ),
)
], ],
).paddingAll(24.h),), ).paddingAll(24.h),),
), ),

@ -290,7 +290,7 @@ class TextInputWidget extends StatelessWidget {
} }
Widget _buildLabelText(Color? labelColor) { 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) { Widget _buildTextField(BuildContext context) {

@ -1,63 +1,263 @@
import 'dart:async'; import 'dart:async';
import 'package:easy_localization/easy_localization.dart';
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import 'package:hmg_patient_app_new/core/app_export.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/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: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 CameraPosition currentLocation;
final Function(CameraPosition) onCameraMoved; final Function(CameraPosition) onCameraMoved;
final VoidCallback onCameraIdle; final VoidCallback onCameraIdle;
final MapType mapType; final MapType initialMapType;
final bool compassEnabled; final bool compassEnabled;
final bool myLocationEnabled; final bool myLocationEnabled;
final bool showCenterMarker; final bool showCenterMarker;
final Completer<HuaweiMapController>? inputController; final Completer<HuaweiMapController>? inputController;
HMSMap({super.key, required this.currentLocation ,required this.onCameraMoved,required this.onCameraIdle, const HMSMap({
this.mapType = MapType.normal,this.compassEnabled = false,this.showCenterMarker = false, super.key,
this.myLocationEnabled = true, this.inputController}); 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 @override
State<HMSMap> createState() => _HMSMapState(); State<HMSMap> createState() => _HMSMapState();
} }
class _HMSMapState extends State<HMSMap> { class _HMSMapState extends State<HMSMap> {
Completer<HuaweiMapController>? controller; Completer<HuaweiMapController>? 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 @override
void initState() { void initState() {
super.initState();
HuaweiMapInitializer.initializeMap(); HuaweiMapInitializer.initializeMap();
controller = widget.inputController ?? Completer<HuaweiMapController>(); controller = widget.inputController ?? Completer<HuaweiMapController>();
super.initState(); _selectedMapType = widget.initialMapType;
}
void _onMapTypeSelected(MapType type) {
setState(() {
_selectedMapType = type;
_showMapTypeSelector = false;
});
} }
// @override
void _toggleSelector() =>
setState(() => _showMapTypeSelector = !_showMapTypeSelector);
@override @override
Widget build(BuildContext context) => Widget build(BuildContext context) {
Stack( return Stack(
children: [ children: [
HuaweiMap( // Huawei Map
mapType: widget.mapType, HuaweiMap(
zoomControlsEnabled: false, mapType: _selectedMapType,
myLocationEnabled: widget.myLocationEnabled, zoomControlsEnabled: false,
myLocationButtonEnabled: false, myLocationEnabled: widget.myLocationEnabled,
compassEnabled: widget.compassEnabled, myLocationButtonEnabled: false,
onCameraIdle:()=> widget.onCameraIdle(), compassEnabled: widget.compassEnabled,
initialCameraPosition: widget.currentLocation, onCameraIdle: widget.onCameraIdle,
onCameraMove: (value) => widget.onCameraMoved(value), initialCameraPosition: widget.currentLocation,
onMapCreated: (HuaweiMapController controller) { onCameraMove: widget.onCameraMoved,
this.controller?.complete(controller); 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<MapType> 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,
});
} }

@ -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<GoogleMapController>? 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<GoogleMapController>? inputController}){
// controller = inputController ?? Completer<GoogleMapController>();
// }
//
// @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 'dart:async';
import 'package:easy_localization/easy_localization.dart';
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import 'package:google_maps_flutter/google_maps_flutter.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/core/utils/size_utils.dart';
import 'package:hmg_patient_app_new/extensions/widget_extensions.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:hmg_patient_app_new/theme/colors.dart';
class GMSMap extends StatelessWidget{ class GMSMap extends StatefulWidget {
Completer<GoogleMapController>? controller;
final CameraPosition currentLocation; final CameraPosition currentLocation;
final Function(CameraPosition) onCameraMoved; final Function(CameraPosition) onCameraMoved;
final VoidCallback onCameraIdle; final VoidCallback onCameraIdle;
final MapType mapType; final MapType initialMapType;
final bool compassEnabled; final bool compassEnabled;
final bool myLocationEnabled; final bool myLocationEnabled;
final bool showCenterMarker; final bool showCenterMarker;
final Completer<GoogleMapController>? 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<GMSMap> createState() => _GMSMapState();
}
class _GMSMapState extends State<GMSMap> {
late Completer<GoogleMapController> _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, @override
this.mapType = MapType.normal,this.compassEnabled = false,this.showCenterMarker = false, void initState() {
this.myLocationEnabled = true,Completer<GoogleMapController>? inputController}){ super.initState();
controller = inputController ?? Completer<GoogleMapController>(); _controller = widget.inputController ?? Completer<GoogleMapController>();
_selectedMapType = widget.initialMapType;
}
void _onMapTypeSelected(MapType type) {
setState(() {
_selectedMapType = type;
_showMapTypeSelector = false;
});
}
void _toggleMapTypeSelector() {
setState(() => _showMapTypeSelector = !_showMapTypeSelector);
} }
@override @override
Widget build(BuildContext context) { Widget build(BuildContext context) {
return Stack( return Stack(
children: [ children: [
GoogleMap( // Google Map
mapType: mapType, GoogleMap(
mapType: _selectedMapType,
zoomControlsEnabled: true, zoomControlsEnabled: true,
myLocationEnabled: myLocationEnabled, myLocationEnabled: widget.myLocationEnabled,
myLocationButtonEnabled: false, myLocationButtonEnabled: false,
compassEnabled: compassEnabled, compassEnabled: widget.compassEnabled,
initialCameraPosition: currentLocation, initialCameraPosition: widget.currentLocation,
onCameraMove: (value) => onCameraMoved(value), onCameraMove: widget.onCameraMoved,
onCameraIdle: ()=>onCameraIdle(), onCameraIdle: widget.onCameraIdle,
// padding: EdgeInsets.only(bottom: 300.h),
onMapCreated: (GoogleMapController controller) { onMapCreated: (GoogleMapController controller) {
this.controller?.complete(controller); _controller.complete(controller);
}, },
), ),
Visibility(
visible: showCenterMarker, // Centre pin marker
child: Align( if (widget.showCenterMarker)
alignment: Alignment.center, Align(
child: Icon(Icons.location_pin, size: 36.h, color: AppColors.primaryRedColor).paddingOnly(bottom: 24.h), 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<MapType> 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,
});
} }
Loading…
Cancel
Save