You cannot select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
263 lines
8.5 KiB
Dart
263 lines
8.5 KiB
Dart
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';
|
|
|
|
class HMSMap extends StatefulWidget {
|
|
final CameraPosition currentLocation;
|
|
final Function(CameraPosition) onCameraMoved;
|
|
final VoidCallback onCameraIdle;
|
|
final MapType initialMapType;
|
|
final bool compassEnabled;
|
|
final bool myLocationEnabled;
|
|
final bool showCenterMarker;
|
|
final Completer<HuaweiMapController>? 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<HMSMap> createState() => _HMSMapState();
|
|
}
|
|
|
|
class _HMSMapState extends State<HMSMap> {
|
|
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
|
|
void initState() {
|
|
super.initState();
|
|
HuaweiMapInitializer.initializeMap();
|
|
controller = widget.inputController ?? Completer<HuaweiMapController>();
|
|
_selectedMapType = widget.initialMapType;
|
|
}
|
|
|
|
void _onMapTypeSelected(MapType type) {
|
|
setState(() {
|
|
_selectedMapType = type;
|
|
_showMapTypeSelector = false;
|
|
});
|
|
}
|
|
|
|
void _toggleSelector() =>
|
|
setState(() => _showMapTypeSelector = !_showMapTypeSelector);
|
|
|
|
@override
|
|
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(),
|
|
),
|
|
],
|
|
),
|
|
),
|
|
],
|
|
);
|
|
}
|
|
}
|
|
|
|
// ── 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,
|
|
});
|
|
} |