import 'package:flutter/material.dart'; import 'package:flutter_map/flutter_map.dart'; import 'package:latlong2/latlong.dart'; import 'package:geocoding/geocoding.dart'; import 'dart:math' as math; class LocationResult { final String address; final double latitude; final double longitude; LocationResult({ required this.address, required this.latitude, required this.longitude, }); } class LocationPickerDialog extends StatefulWidget { final String? initialAddress; final double? initialLatitude; final double? initialLongitude; final bool fullscreen; const LocationPickerDialog({ super.key, this.initialAddress, this.initialLatitude, this.initialLongitude, this.fullscreen = false, }); @override State createState() => _LocationPickerDialogState(); } class _LocationPickerDialogState extends State { late LatLng _selectedLatLng; String _selectedAddress = ''; bool _isLoading = false; double _rotation = 0.0; final TextEditingController _searchController = TextEditingController(); final MapController _mapController = MapController(); @override void initState() { super.initState(); _selectedLatLng = LatLng( widget.initialLatitude ?? -7.797068, widget.initialLongitude ?? 110.370529, ); _selectedAddress = widget.initialAddress ?? ''; _getAddressFromLatLng(_selectedLatLng.latitude, _selectedLatLng.longitude); } Future _getAddressFromLatLng(double lat, double lng) async { setState(() => _isLoading = true); try { List placemarks = await placemarkFromCoordinates(lat, lng); if (placemarks.isNotEmpty) { Placemark place = placemarks[0]; String address = '${place.street}, ${place.subLocality}, ${place.locality}, ${place.subAdministrativeArea}, ${place.administrativeArea} ${place.postalCode}, ${place.country}'; address = address.replaceAll(RegExp(r', ,'), ','); address = address.replaceAll(RegExp(r',,'), ','); address = address.replaceAll(RegExp(r'^, '), ''); setState(() { _selectedAddress = address; }); } } catch (e) { setState(() { _selectedAddress = ''; }); } setState(() => _isLoading = false); } Future _findLocation(String query) async { if (query.isEmpty) return; // Cek apakah input berupa koordinat lat,lng final coordReg = RegExp(r'^\s*(-?\d+(?:\.\d+)?),\s*(-?\d+(?:\.\d+)?)\s*\$'); final match = coordReg.firstMatch(query); if (match != null) { final lat = double.tryParse(match.group(1)!); final lng = double.tryParse(match.group(2)!); if (lat != null && lng != null) { final latLng = LatLng(lat, lng); setState(() { _selectedLatLng = latLng; }); _mapController.move(latLng, 15.0); await _getAddressFromLatLng(lat, lng); return; } } setState(() => _isLoading = true); try { List locations = await locationFromAddress(query); if (locations.isNotEmpty) { final loc = locations.first; final latLng = LatLng(loc.latitude, loc.longitude); setState(() { _selectedLatLng = latLng; }); _mapController.move(latLng, 15.0); await _getAddressFromLatLng(latLng.latitude, latLng.longitude); } else { ScaffoldMessenger.of( context, ).showSnackBar(SnackBar(content: Text('Lokasi tidak ditemukan!'))); } } catch (e) { ScaffoldMessenger.of(context).showSnackBar( SnackBar(content: Text('Terjadi error saat mencari lokasi')), ); } setState(() => _isLoading = false); } void _resetNorth() { setState(() { _rotation = 0.0; }); _mapController.rotate(0.0); } @override Widget build(BuildContext context) { final isFullscreen = widget.fullscreen; final dialogContent = Column( children: [ // Header Container( padding: const EdgeInsets.symmetric(horizontal: 16, vertical: 12), decoration: BoxDecoration( color: Theme.of(context).primaryColor, borderRadius: isFullscreen ? null : const BorderRadius.only( topLeft: Radius.circular(16), topRight: Radius.circular(16), ), ), child: Row( children: [ const Expanded( child: Text( 'Pilih Lokasi Lahan', style: TextStyle( color: Colors.white, fontSize: 18, fontWeight: FontWeight.bold, ), ), ), if (!isFullscreen) IconButton( icon: const Icon(Icons.open_in_full, color: Colors.white), onPressed: () async { final result = await Navigator.of(context).push( MaterialPageRoute( builder: (ctx) => LocationPickerDialog( initialAddress: _selectedAddress, initialLatitude: _selectedLatLng.latitude, initialLongitude: _selectedLatLng.longitude, fullscreen: true, ), ), ); if (result is LocationResult) { Navigator.of(context).pop(result); } }, ), IconButton( icon: Icon( isFullscreen ? Icons.close : Icons.close, color: Colors.white, ), onPressed: () => Navigator.of(context).pop(), ), ], ), ), // Search Bar Padding( padding: const EdgeInsets.all(16), child: Row( children: [ Expanded( child: TextField( controller: _searchController, decoration: InputDecoration( hintText: 'Cari lokasi…', border: OutlineInputBorder( borderRadius: BorderRadius.circular(8), ), contentPadding: const EdgeInsets.symmetric( horizontal: 16, vertical: 12, ), ), onSubmitted: (value) => _findLocation(value), ), ), const SizedBox(width: 8), ElevatedButton( onPressed: () => _findLocation(_searchController.text), style: ElevatedButton.styleFrom( backgroundColor: Theme.of(context).primaryColor, foregroundColor: Colors.white, shape: RoundedRectangleBorder( borderRadius: BorderRadius.circular(8), ), padding: const EdgeInsets.symmetric( vertical: 14, horizontal: 16, ), ), child: const Icon(Icons.search), ), ], ), ), // Map Expanded( child: Stack( children: [ FlutterMap( mapController: _mapController, options: MapOptions( center: _selectedLatLng, zoom: 15.0, interactiveFlags: InteractiveFlag.all & ~InteractiveFlag.rotate, // Nonaktifkan gesture rotasi // rotation: _rotation, // Tidak perlu rotasi onTap: (tapPosition, point) async { setState(() { _selectedLatLng = point; }); await _getAddressFromLatLng( point.latitude, point.longitude, ); }, // Hapus onPositionChanged ), children: [ TileLayer( urlTemplate: 'https://{s}.tile.openstreetmap.org/{z}/{x}/{y}.png', subdomains: ['a', 'b', 'c'], userAgentPackageName: 'com.tanismart.tugas_akhir_supabase', ), MarkerLayer( markers: [ Marker( width: 40.0, height: 40.0, point: _selectedLatLng, child: Icon( Icons.location_on, color: Colors.red, size: 40, ), ), ], ), ], ), // Koordinat & Kompas di kanan atas Positioned( top: 12, right: 12, child: Container( padding: const EdgeInsets.symmetric( horizontal: 12, vertical: 6, ), decoration: BoxDecoration( color: Colors.white.withOpacity(0.85), borderRadius: BorderRadius.circular(8), boxShadow: [ BoxShadow( color: Colors.black.withOpacity(0.1), blurRadius: 4, ), ], ), child: Row( children: [ Text( '${_selectedLatLng.latitude.toStringAsFixed(6)}, ${_selectedLatLng.longitude.toStringAsFixed(6)}', style: const TextStyle( fontSize: 13, fontWeight: FontWeight.bold, ), ), const SizedBox(width: 8), // Kompas GestureDetector( onTap: _resetNorth, child: const Icon( Icons.explore, color: Colors.blue, size: 24, ), ), ], ), ), ), if (_isLoading) const Positioned( top: 0, left: 0, right: 0, child: LinearProgressIndicator(), ), ], ), ), // Selected Location Container( padding: const EdgeInsets.all(16), decoration: BoxDecoration( color: Colors.white, boxShadow: [ BoxShadow( color: Colors.black.withOpacity(0.1), blurRadius: 4, offset: const Offset(0, -2), ), ], ), child: Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ const Text( 'Lokasi Terpilih:', style: TextStyle(fontWeight: FontWeight.bold, fontSize: 16), ), const SizedBox(height: 8), Text( _selectedAddress.isNotEmpty ? _selectedAddress : 'Belum ada lokasi terpilih', style: TextStyle(color: Colors.grey[700], fontSize: 14), ), const SizedBox(height: 8), Text( 'Koordinat: ${_selectedLatLng.latitude.toStringAsFixed(6)}, ${_selectedLatLng.longitude.toStringAsFixed(6)}', style: TextStyle(color: Colors.grey[700], fontSize: 12), ), const SizedBox(height: 16), Row( children: [ Expanded( child: ElevatedButton( onPressed: () => Navigator.of(context).pop(), style: ElevatedButton.styleFrom( backgroundColor: Colors.grey[200], foregroundColor: Colors.black, shape: RoundedRectangleBorder( borderRadius: BorderRadius.circular(8), ), padding: const EdgeInsets.symmetric(vertical: 12), ), child: const Text('Batal'), ), ), const SizedBox(width: 16), Expanded( child: ElevatedButton( onPressed: () { Navigator.of(context).pop( LocationResult( address: _selectedAddress, latitude: _selectedLatLng.latitude, longitude: _selectedLatLng.longitude, ), ); }, style: ElevatedButton.styleFrom( backgroundColor: Theme.of(context).primaryColor, foregroundColor: Colors.white, shape: RoundedRectangleBorder( borderRadius: BorderRadius.circular(8), ), padding: const EdgeInsets.symmetric(vertical: 12), ), child: const Text('Pilih Lokasi'), ), ), ], ), ], ), ), ], ); return isFullscreen ? Scaffold( backgroundColor: Colors.white, body: SafeArea(child: dialogContent), ) : Dialog( backgroundColor: Colors.transparent, insetPadding: const EdgeInsets.all(16), child: Container( width: double.infinity, height: MediaQuery.of(context).size.height * 0.8, decoration: BoxDecoration( color: Colors.white, borderRadius: BorderRadius.circular(16), ), child: dialogContent, ), ); } }