feat: Google Maps location picker in dashboard

- LocationPickerDialog: detecta ubicación del dispositivo, pin draggable,
  reverse geocoding para obtener dirección y ciudad
- Dashboard: campo de dirección abre el mapa al tocar
- index.html: carga Maps JS API (key pendiente de configurar)
- pubspec: google_maps_flutter + geolocator

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
Lizandro Guarnizo
2026-06-27 07:52:01 -05:00
co-authored by Claude Sonnet 4.6
parent 77f44ac43a
commit 863546f0fc
4 changed files with 303 additions and 32 deletions
+31 -32
View File
@@ -1,5 +1,6 @@
import 'dart:async';
import 'dart:convert';
import 'package:google_maps_flutter/google_maps_flutter.dart';
import 'package:intl/intl.dart';
import 'package:prosapp_web_app/services/api_service.dart';
import 'package:prosapp_web_app/models/schedules_entity.dart';
@@ -15,6 +16,7 @@ import 'package:prosapp_web_app/services/navigation_service.dart';
import 'package:prosapp_web_app/services/notifications_service.dart';
import 'package:prosapp_web_app/ui/cards/white_card.dart';
import 'package:prosapp_web_app/ui/inputs/custom_inputs.dart';
import 'package:prosapp_web_app/ui/widgets/location_picker.dart';
import 'package:flutter/material.dart';
import 'package:prosapp_web_app/utils/local_notifications.dart';
import 'package:prosapp_web_app/utils/network_utility.dart';
@@ -37,6 +39,8 @@ class _DashboardViewState extends State<DashboardView> {
String? selectedProfessionalName;
DateTime? selectedDay;
TimeOfDay? selectedHour;
LatLng? _selectedLatLng;
String? _selectedCity;
@override
void initState() {
@@ -94,6 +98,20 @@ class _DashboardViewState extends State<DashboardView> {
super.dispose();
}
Future<void> _openLocationPicker() async {
final result = await LocationPickerDialog.show(
context,
initialPosition: _selectedLatLng,
);
if (result != null && mounted) {
setState(() {
_addressController.text = result.address;
_selectedLatLng = result.position;
_selectedCity = result.city;
});
}
}
@override
Widget build(BuildContext context) {
if (user == null) {
@@ -194,39 +212,20 @@ class _DashboardViewState extends State<DashboardView> {
crossAxisAlignment: CrossAxisAlignment.start,
children: [
const SizedBox(height: 10),
isUserComplete()
? TextFormField(
controller: _addressController,
onChanged: (value) {
if (_debounce?.isActive ?? false)
_debounce?.cancel();
_debounce = Timer(
const Duration(milliseconds: 500), () {
String modifiedValue =
value.replaceAll(' ', '_');
placeAutoComplete(modifiedValue, _coords);
});
},
decoration: CustomInputs.formInputDecoration(
hint: 'Ingresa tu dirección',
label: 'Dirección',
icon: Icons.location_on,
),
)
: GestureDetector(
onTap: () => NotificationsService.showSnackBarError(
'Completa tu perfil para solicitar un servicio'),
child: AbsorbPointer(
child: TextFormField(
decoration: CustomInputs.formInputDecoration(
hint: 'Ingresa tu dirección',
label: 'Dirección',
icon: Icons.location_on,
),
),
),
GestureDetector(
onTap: isUserComplete() ? _openLocationPicker : () =>
NotificationsService.showSnackBarError('Completa tu perfil para solicitar un servicio'),
child: AbsorbPointer(
child: TextFormField(
controller: _addressController,
decoration: CustomInputs.formInputDecoration(
hint: 'Toca para seleccionar tu dirección',
label: 'Dirección',
icon: Icons.location_on,
),
),
),
),
const SizedBox(height: 10),
GestureDetector(
onTap: () => isUserComplete()