import 'dart:async'; import 'dart:developer'; import 'package:easy_localization/src/public_ext.dart'; import 'package:flutter/material.dart'; import 'package:geolocator/geolocator.dart'; import 'package:google_maps_flutter/google_maps_flutter.dart'; import 'package:mc_common_app/classes/app_state.dart'; import 'package:mc_common_app/generated/locale_keys.g.dart'; import 'package:mc_common_app/utils/navigator.dart'; import 'package:mc_common_app/widgets/button/show_fill_button.dart'; import 'package:mc_common_app/widgets/common_widgets/app_bar.dart'; import 'package:permission_handler/permission_handler.dart'; import 'map_selection_widget.dart'; import 'package:geocoding/geocoding.dart'; class PickLocationPage extends StatefulWidget { final Function(double, double, String) onPickAddress; const PickLocationPage({super.key, required this.onPickAddress}); @override State createState() => _PickLocationPageState(); } class _PickLocationPageState extends State { double latitude = 0; double longitude = 0; final Set markers = {}; late AppMap appMap; static CameraPosition _kGooglePlex = CameraPosition(target: AppState().currentLocation, zoom: 14.4746); late LatLng currentPosition; Completer mapController = Completer(); final ValueNotifier _counter = ValueNotifier(""); final ValueNotifier _isLoadingAddress = ValueNotifier(false); String actualAddress = ""; // Store the actual address to return @override void initState() { appMap = AppMap( _kGooglePlex.toMap() as Map, onCameraMove: (camera) { _updatePosition(camera); }, onCameraIdle: () { updateAddress(latitude, longitude); }, onMapCreated: () { // goToCurrentLocation(); _getUserLocation(); setState(() {}); }, ); super.initState(); } @override void dispose() { super.dispose(); } @override Widget build(BuildContext context) { return Scaffold( appBar: CustomAppBar( title: LocaleKeys.pickLocation.tr(), isRemoveBackButton: false, ), body: Column( children: [ Expanded( child: Stack( children: [ appMap, ValueListenableBuilder( builder: (BuildContext context, String value, Widget? child) { // This builder will only get called when the _counter // is updated. return value.isNotEmpty ? Container( width: double.infinity, margin: const EdgeInsets.all(12), child: Card( child: Padding( padding: const EdgeInsets.all(12.0), child: Text(value), )), ) : Container(); }, valueListenable: _counter, ), ValueListenableBuilder( builder: (BuildContext context, bool isLoading, Widget? child) { return isLoading ? Container( width: double.infinity, margin: const EdgeInsets.only(top: 70), alignment: Alignment.topCenter, child: const Card( child: Padding( padding: EdgeInsets.all(12.0), child: Row( mainAxisSize: MainAxisSize.min, children: [ SizedBox( width: 20, height: 20, child: CircularProgressIndicator(strokeWidth: 2), ), SizedBox(width: 12), Text('Loading address...'), ], ), ), ), ) : const SizedBox.shrink(); }, valueListenable: _isLoadingAddress, ), const Align( alignment: Alignment.center, child: Icon( Icons.place, color: Colors.red, size: 50, ), ), ], ), ), SizedBox( width: double.infinity, child: Padding( padding: const EdgeInsets.all(12.0), child: ValueListenableBuilder( valueListenable: _isLoadingAddress, builder: (context, isLoading, child) { return ShowFillButton( maxHeight: 55, title: isLoading ? 'Loading Address...' : LocaleKeys.pickAddress.tr(), isDisabled: isLoading || _counter.value.isEmpty, onPressed: () { if (!isLoading && _counter.value.isNotEmpty) { widget.onPickAddress(latitude, longitude, actualAddress); pop(context); } }, ); }, ), ), ), ], ), ); } Future _updatePosition(CameraPosition position) async { latitude = position.target.latitude; longitude = position.target.longitude; log(latitude.toString()); log(latitude.toString()); // updateAddress(latitude, longitude); } void _getUserLocation() async { if (await Permission.location.request().isGranted) { var position = await GeolocatorPlatform.instance.getCurrentPosition(); currentPosition = LatLng(position.latitude, position.longitude); latitude = position.latitude; longitude = position.longitude; setMap(); updateAddress(latitude, longitude); } else { requestPermissions().then( (value) async { if (value[Permission.location]!.isGranted) { var position = await GeolocatorPlatform.instance.getCurrentPosition(); currentPosition = LatLng(position.latitude, position.longitude); latitude = position.latitude; longitude = position.longitude; setMap(); updateAddress(latitude, longitude); } }, ); } setState(() {}); } updateAddress(double latitude, double longitude) async { _isLoadingAddress.value = true; try { List placemarks = await placemarkFromCoordinates(latitude, longitude).timeout( const Duration(seconds: 5), onTimeout: () { throw TimeoutException('Geocoding request timed out'); }, ); if (placemarks.isNotEmpty) { final placemark = placemarks.first; // Restore original format - building address string from individual fields actualAddress = '${placemark.street}, ${placemark.subLocality}, ${placemark.locality}, ${placemark.postalCode}, ${placemark.country}'; _counter.value = actualAddress; } else { actualAddress = ""; _counter.value = ""; } } catch (e) { log('Error getting address: $e'); // On error, leave address empty - user can try again by moving the map actualAddress = ""; _counter.value = ""; } finally { _isLoadingAddress.value = false; } } setMap() { _kGooglePlex = CameraPosition( target: currentPosition, zoom: 14.4746, ); appMap.moveTo(cameraPostion: _kGooglePlex); } Future getCurrentAddress() async { // List placemarks = await placemarkFromCoordinates(latitude, longitude); // return placemarks.first.name ?? ""; return ""; } } Future> requestPermissions() async { var permissionResults = [Permission.location].request(); return permissionResults; }