import 'dart:async'; import 'package:flutter/material.dart'; import 'package:geocoding/geocoding.dart'; import 'package:maplibre_gl/maplibre_gl.dart'; import '../../core/location/location_access_service.dart'; import '../../core/maps/open_free_map.dart'; import '../../core/theme/app_theme.dart'; import 'tenant_location_data.dart'; Future showLocationPickerSheet( BuildContext context, { TenantLocationData? initialLocation, String title = 'Konum Seç', }) { return showModalBottomSheet( context: context, isScrollControlled: true, backgroundColor: Colors.transparent, builder: (_) => _LocationPickerSheet( initialLocation: initialLocation, title: title, ), ); } class _LocationPickerSheet extends StatefulWidget { const _LocationPickerSheet({ required this.initialLocation, required this.title, }); final TenantLocationData? initialLocation; final String title; @override State<_LocationPickerSheet> createState() => _LocationPickerSheetState(); } class _LocationPickerSheetState extends State<_LocationPickerSheet> { static const _fallback = LatLng(41.0082, 28.9784); MapLibreMapController? _mapController; TenantLocationData? _selection; bool _styleReady = false; bool _locating = false; bool _resolvingAddress = false; String? _error; LatLng get _selectedPoint => LatLng( _selection?.latitude ?? widget.initialLocation?.latitude ?? _fallback.latitude, _selection?.longitude ?? widget.initialLocation?.longitude ?? _fallback.longitude, ); @override void initState() { super.initState(); _selection = widget.initialLocation; if (_selection?.hasCoordinates == true && ((_selection?.address ?? '').trim().isEmpty)) { unawaited(_updateAddress(_selectedPoint)); } } Future _pickCurrentLocation() async { setState(() { _locating = true; _error = null; }); try { final position = await LocationAccessService.getCurrentPosition(); final point = LatLng(position.latitude, position.longitude); await _moveCamera(point, 14); setState(() { _selection = (_selection ?? const TenantLocationData()).copyWith( latitude: point.latitude, longitude: point.longitude, ); }); await _refreshSelectionMarker(); await _updateAddress(point); } catch (e) { setState(() => _error = e.toString()); } finally { if (mounted) setState(() => _locating = false); } } Future _moveCamera(LatLng point, double zoom) async { final controller = _mapController; if (controller == null) return; await controller.animateCamera( CameraUpdate.newLatLngZoom(point, zoom), ); } Future _refreshSelectionMarker() async { final controller = _mapController; if (controller == null || !_styleReady || _selection?.hasCoordinates != true) { return; } await controller.clearCircles(); await controller.addCircle( CircleOptions( geometry: _selectedPoint, circleRadius: 8, circleColor: '#4F46E5', circleStrokeWidth: 3, circleStrokeColor: '#FFFFFF', ), ); } Future _selectPoint(LatLng point, {double zoom = 15}) async { setState(() { _selection = (_selection ?? const TenantLocationData()).copyWith( latitude: point.latitude, longitude: point.longitude, ); }); await _refreshSelectionMarker(); await _moveCamera(point, zoom); await _updateAddress(point); } Future _updateAddress(LatLng point) async { setState(() { _resolvingAddress = true; _error = null; }); try { final placemarks = await placemarkFromCoordinates( point.latitude, point.longitude, ); final placemark = placemarks.isNotEmpty ? placemarks.first : null; final addressParts = [ placemark?.street, placemark?.subLocality, placemark?.locality, ].where((part) => (part ?? '').trim().isNotEmpty).cast().toList(); setState(() { _selection = (_selection ?? const TenantLocationData()).copyWith( latitude: point.latitude, longitude: point.longitude, address: addressParts.join(', '), district: placemark?.subAdministrativeArea?.trim().isNotEmpty == true ? placemark!.subAdministrativeArea!.trim() : placemark?.subLocality?.trim(), city: placemark?.administrativeArea?.trim().isNotEmpty == true ? placemark!.administrativeArea!.trim() : placemark?.locality?.trim(), ); }); } catch (_) { setState(() { _selection = (_selection ?? const TenantLocationData()).copyWith( latitude: point.latitude, longitude: point.longitude, ); }); } finally { if (mounted) setState(() => _resolvingAddress = false); } } @override Widget build(BuildContext context) { final bottom = MediaQuery.paddingOf(context).bottom; return Container( height: MediaQuery.sizeOf(context).height * 0.88, decoration: const BoxDecoration( color: AppColors.surface, borderRadius: BorderRadius.vertical(top: Radius.circular(24)), ), child: Column( children: [ const SizedBox(height: 12), Container( width: 42, height: 4, decoration: BoxDecoration( color: AppColors.border, borderRadius: BorderRadius.circular(2), ), ), Padding( padding: const EdgeInsets.fromLTRB(16, 16, 16, 12), child: Row( children: [ Expanded( child: Text( widget.title, style: const TextStyle( fontSize: 18, fontWeight: FontWeight.w700, color: AppColors.textPrimary, ), ), ), TextButton.icon( onPressed: _locating ? null : _pickCurrentLocation, icon: _locating ? const SizedBox( width: 16, height: 16, child: CircularProgressIndicator(strokeWidth: 2), ) : const Icon(Icons.my_location_rounded, size: 18), label: const Text('Konumum'), ), ], ), ), Expanded( child: Padding( padding: const EdgeInsets.symmetric(horizontal: 16), child: ClipRRect( borderRadius: BorderRadius.circular(16), child: Stack( children: [ MapLibreMap( styleString: OpenFreeMap.libertyStyle, initialCameraPosition: CameraPosition( target: _selectedPoint, zoom: 13, ), onMapCreated: (controller) => _mapController = controller, onStyleLoadedCallback: () async { _styleReady = true; await _refreshSelectionMarker(); }, onMapClick: (_, point) async { await _selectPoint(point); }, compassEnabled: false, tiltGesturesEnabled: false, rotateGesturesEnabled: false, myLocationEnabled: false, myLocationTrackingMode: MyLocationTrackingMode.none, ), Positioned( top: 12, left: 12, right: 12, child: Container( padding: const EdgeInsets.symmetric( horizontal: 12, vertical: 10, ), decoration: BoxDecoration( color: Colors.white.withValues(alpha: 0.92), borderRadius: BorderRadius.circular(12), ), child: const Text( 'Haritada bir noktaya dokunarak konum seçin.', style: TextStyle( fontSize: 12, color: AppColors.textSecondary, ), ), ), ), ], ), ), ), ), Padding( padding: const EdgeInsets.fromLTRB(16, 16, 16, 8), child: Container( width: double.infinity, padding: const EdgeInsets.all(14), decoration: BoxDecoration( color: AppColors.background, borderRadius: BorderRadius.circular(16), border: Border.all(color: AppColors.border), ), child: Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ Row( children: [ const Icon(Icons.place_outlined, size: 18, color: AppColors.accent), const SizedBox(width: 8), const Text( 'Seçilen Konum', style: TextStyle( fontWeight: FontWeight.w600, color: AppColors.textPrimary, ), ), if (_resolvingAddress) ...[ const SizedBox(width: 10), const SizedBox( width: 14, height: 14, child: CircularProgressIndicator(strokeWidth: 2), ), ], ], ), const SizedBox(height: 10), Text( _selection?.fullLabel.isNotEmpty == true ? _selection!.fullLabel : 'Haritada bir nokta seçin.', style: const TextStyle(color: AppColors.textSecondary), ), const SizedBox(height: 10), Text( 'Koordinatlar: ' '${_selectedPoint.latitude.toStringAsFixed(6)}, ' '${_selectedPoint.longitude.toStringAsFixed(6)}', style: const TextStyle( fontSize: 12, color: AppColors.textMuted, ), ), if (_error != null) ...[ const SizedBox(height: 10), Text( _error!, style: const TextStyle(color: AppColors.cancelled), ), ], ], ), ), ), Padding( padding: EdgeInsets.fromLTRB(16, 8, 16, bottom + 12), child: FilledButton( onPressed: _selection?.hasCoordinates == true ? () => Navigator.of(context).pop(_selection) : null, style: FilledButton.styleFrom( minimumSize: const Size(double.infinity, 50), ), child: const Text('Bu Konumu Kullan'), ), ), ], ), ); } }