import 'dart:async'; import 'dart:convert'; import 'dart:io'; import 'package:cloud_firestore/cloud_firestore.dart'; import 'package:firebase_auth/firebase_auth.dart'; import 'package:firebase_messaging/firebase_messaging.dart'; import 'package:flutter/cupertino.dart'; import 'package:flutter/material.dart'; import 'package:geocoding/geocoding.dart' as geocoding; import 'package:http/http.dart' as http; import 'package:geolocator/geolocator.dart'; import 'package:google_maps_flutter/google_maps_flutter.dart'; import 'package:flutter_polyline_points/flutter_polyline_points.dart'; import 'package:intl/intl.dart'; import 'package:location/location.dart'; import 'package:package_info_plus/package_info_plus.dart'; import 'package:prosappco/constansts.dart'; import 'package:prosappco/src/authentication/authentication_repository.dart'; import 'package:prosappco/src/components/network_utility.dart'; import 'package:prosappco/src/models/event_model.dart'; import 'package:prosappco/src/models/setting_model.dart'; import 'package:prosappco/src/models/user_model.dart'; import 'package:prosappco/src/presentation/screens/professional.dart'; import 'package:prosappco/src/presentation/screens/profile/profile.dart'; import 'package:prosappco/src/presentation/screens/service_after.dart'; import 'package:prosappco/src/presentation/screens/service_type.dart'; import 'package:prosappco/src/presentation/widgets/shared/drawer_menu.dart'; import 'package:prosappco/src/presentation/widgets/shared/primary_button.dart'; import 'package:prosappco/src/presentation/widgets/shared/warning_snackbar.dart'; import 'package:prosappco/src/providers/user_provider.dart'; import 'package:provider/provider.dart'; import 'package:url_launcher/url_launcher.dart'; class ServiceScreen extends StatefulWidget { const ServiceScreen({super.key}); @override State createState() => _ServiceScreenState(); } class _ServiceScreenState extends State { late String appVersion; final Uri _url = Uri.parse( 'https://play.google.com/store/apps/details?id=com.prosapp.prosapp'); final Uri _urlIos = Uri.parse('https://apps.apple.com/co/app/prosapp/id6469028900'); final Location _locationController = Location(); final Completer _mapController = Completer(); static const LatLng _pGooglePlex = LatLng(7.080486, -73.087447); LatLng? _currentP; Map polylines = {}; final TextEditingController _locacionController = TextEditingController(); final TextEditingController _ubicationController = TextEditingController(); final TextEditingController _profesionalController = TextEditingController(); final TextEditingController _serviceTypeController = TextEditingController(); final TextEditingController _observacionController = TextEditingController(); final DateTime now = DateTime.now(); EventoService eventoService = EventoService(); String _serviceType = 'Servicio'; String ubicacion = ''; String professionalId = ''; String professionalToken = ''; String professionalAddress = ''; String professionalUbicacion = ''; int? professionalTarifa; double? professionalLatitude; double? professionalLongitude; DateTime? _selectedDate; String _ciudad = '...'; TimeOfDay? _selectedTime; final DateFormat formatter = DateFormat('dd/MM/yyyy'); dynamic coordinates; Set markers = {}; BitmapDescriptor? _markerIcon; List _placesList = []; String _coordsOfCity = '0.0,0.0'; final uid = AuthenticationRepository.instance.getCurrentUserUid(); String? settings; late final FirebaseAuth _auth; final _nameController = TextEditingController(); @override void initState() { super.initState(); _getAppVersion(); getLocationUpdates(); _auth = FirebaseAuth.instance; final currentUser = _auth.currentUser; if (currentUser != null && currentUser.displayName != null) { _nameController.text = currentUser.displayName!; } if (settings == null) { SettingModel.getSettings().then( (SettingModel value) => (value) { if (mounted) { setState(() { settings = value.version; _getAppVersion(); }); } }, ); } if (_ciudad == '...') { AuthenticationRepository.instance .getCity(uid.toString()) .then((String s) { if (s.isEmpty) { FirebaseFirestore.instance.collection('users').doc(uid).set({ 'city': 'Cúcuta', }).then((_) { if (mounted) { setState(() { _ciudad = 'Cúcuta'; }); } }); } else { if (mounted) { setState(() { _ciudad = s; }); } } }); } if (_coordsOfCity == '0.0,0.0') { AuthenticationRepository.instance .getCoordsOfCity(uid.toString()) .then((String s) { if (mounted) { setState(() { _coordsOfCity = s; _animateInitialCameraToPosition(_coordsOfCity); }); } }); } _saveToken(); if (Platform.isAndroid) { BitmapDescriptor.fromAssetImage( const ImageConfiguration(size: Size(2, 2)), 'images/pro_marke_android.png', ).then((icon) { if (mounted) { setState(() { _markerIcon = icon; }); } }); } else { BitmapDescriptor.fromAssetImage( const ImageConfiguration(size: Size(1, 1)), 'images/pro_marke.png', ).then((icon) { if (mounted) { setState(() { _markerIcon = icon; }); } }); } updateMarkersForServiceType(_serviceType); } @override void dispose() { _locacionController.dispose(); _ubicationController.dispose(); _profesionalController.dispose(); _serviceTypeController.dispose(); _observacionController.dispose(); _locationController.onLocationChanged.listen((_) {}).cancel(); super.dispose(); } @override Widget build(BuildContext context) { final userProvider = Provider.of(context); UserModel? user = userProvider.user; dynamic datos; return SafeArea( child: Scaffold( drawer: DrawerMenu(), body: Column( children: [ Expanded( child: Stack( children: [ GoogleMap( onMapCreated: ((GoogleMapController controller) { _mapController.complete(controller); _applyMapStyle(controller); }), initialCameraPosition: const CameraPosition( target: _pGooglePlex, zoom: 15, ), markers: { ...markers, if (_currentP != null) Marker( markerId: const MarkerId("_currentLocation"), icon: BitmapDescriptor.defaultMarker, position: _currentP!, ), }, polylines: Set.of(polylines.values), onCameraIdle: () { if (professionalAddress == '') { if (coordinates != null) { getLocationName( coordinates.latitude, coordinates.longitude) .then((locationName) { if (mounted) { setState(() { _locacionController.text = locationName; }); } }); } } }, onCameraMove: (position) { if (mounted) { setState(() { coordinates = position.target; }); } }, ), Positioned( top: 10, left: 10, child: Builder( builder: (context) { return ElevatedButton( style: ElevatedButton.styleFrom( backgroundColor: Colors.white, shape: const CircleBorder(), elevation: 3, minimumSize: const Size(50, 50), ), child: const Icon( Icons.menu, color: Colors.black, size: 35, ), onPressed: () { Scaffold.of(context).openDrawer(); }, ); }, ), ), const Positioned( bottom: 25, right: 0, left: 0, top: 0, child: Icon( Icons.location_on, size: 40, color: Color(0xFFFF0000), ), ), Positioned( top: 10, right: 20, child: FloatingActionButton( onPressed: () async { try { Position position = await _determinePosition(); if (mounted) { setState(() { _currentP = LatLng( position.latitude, position.longitude, ); }); _animateCameraToPosition(_currentP!); } } catch (e) { WarningSnackbar.show( title: 'Ubicación desactivada', message: 'Por favor activa la ubicacion de tu telefono.', ); } }, elevation: 0, child: const Icon( Icons.gps_fixed, size: 30, ), ), ), ], ), ), Container( padding: const EdgeInsets.symmetric(horizontal: 30, vertical: 10), color: Colors.white, child: Form( child: Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ TextFormField( controller: _serviceTypeController, readOnly: true, onTap: () async { if (_nameController.text == '') { WarningSnackbar.show( title: 'Completa tu perfil', message: 'Asegurate de llenar todos los campos de tu perfil antes de seleccionar un tipo de servicio.', ); return; } final String? serviceType = await Navigator.push( context, CupertinoPageRoute( builder: (BuildContext context) { return const ServiceTypeScreen(); }, ), ) as String?; if (serviceType != null) { if (mounted) { setState(() { _serviceType = serviceType; _serviceTypeController.text = _serviceType; updateMarkersForServiceType(_serviceType); }); datos = await Navigator.push( context, CupertinoPageRoute( builder: (BuildContext context) { return ProfessionalScreen( profession: _serviceTypeController.text, ); }, ), ); if (datos != null) { Professional? profesional = datos[0]; ubicacion = datos[1]; if (profesional != null) { if (mounted) { setState(() { _profesionalController.text = profesional.name.toString(); professionalId = profesional.id; if (profesional.token != '') { professionalToken = profesional.token!; } professionalTarifa = profesional.tarifa ?? 0; professionalUbicacion = profesional.ubicacion; professionalLatitude = profesional.latitude; professionalLongitude = profesional.longitude; if (ubicacion == 'sitio') { professionalAddress = profesional.realAddress; _locacionController.text = profesional.realAddress; LatLng professionalCoordinates = LatLng( professionalLatitude!, professionalLongitude!); if (professionalLatitude != null && professionalLatitude != 0 && professionalLongitude != null && professionalLongitude != 0) { _animateCameraToPosition( LatLng(professionalLatitude!, professionalLongitude!), ); getPolylinePoints(_currentP!, professionalCoordinates) .then( (coordinates) => { generatePolyLineFromPoints( coordinates), }, ); } } else { polylines.clear(); } }); } } } } } }, decoration: const InputDecoration( prefixIcon: Icon(Icons.room_service_rounded), suffixIcon: Icon(Icons.arrow_drop_down), hintText: 'Tipo de Servicio', ), ), const SizedBox(height: 15), TextFormField( controller: _profesionalController, readOnly: true, onTap: () async { if (_currentP == null) { try { Position position = await _determinePosition(); if (mounted) { setState(() { _currentP = LatLng( position.latitude, position.longitude, ); }); _animateCameraToPosition(_currentP!); } } catch (e) { WarningSnackbar.show( title: 'Ubicación desactivada', message: 'Por favor activa la ubicacion de tu telefono para solicitar un servicio.', ); } return; } if (_nameController.text == '') { WarningSnackbar.show( title: 'Completa tu perfil', message: 'Asegurate de llenar todos los campos de tu perfil antes de seleccionar un profesional.', ); return; } datos = await Navigator.push( context, CupertinoPageRoute( builder: (BuildContext context) { return ProfessionalScreen( profession: _serviceTypeController.text, ); }, ), ); if (datos != null) { Professional? profesional = datos[0]; ubicacion = datos[1]; if (profesional != null) { if (mounted) { setState(() { _profesionalController.text = profesional.name.toString(); professionalId = profesional.id; if (profesional.token != '') { professionalToken = profesional.token!; } professionalTarifa = profesional.tarifa ?? 0; professionalUbicacion = profesional.ubicacion; professionalLatitude = profesional.latitude; professionalLongitude = profesional.longitude; if (ubicacion == 'sitio') { professionalAddress = profesional.realAddress; _locacionController.text = profesional.realAddress; LatLng professionalCoordinates = LatLng( professionalLatitude!, professionalLongitude!); if (professionalLatitude != null && professionalLatitude != 0 && professionalLongitude != null && professionalLongitude != 0) { _animateCameraToPosition( LatLng(professionalLatitude!, professionalLongitude!), ); getPolylinePoints( _currentP!, professionalCoordinates) .then( (coordinates) => { generatePolyLineFromPoints(coordinates), }, ); } } else { polylines.clear(); } }); } } } }, decoration: const InputDecoration( prefixIcon: Icon(Icons.assignment_ind_rounded), suffixIcon: Icon(Icons.arrow_drop_down), hintText: 'Seleccionar profesional', ), ), const SizedBox(height: 15), TextFormField( controller: _locacionController, readOnly: ubicacion == 'sitio' ? true : false, decoration: const InputDecoration( prefixIcon: Icon(Icons.near_me), hintText: 'Dirección', ), onChanged: (value) { String modifiedValue = value.replaceAll(' ', '_'); placeAutoComplete(modifiedValue); }, ), ubicacion == 'sitio' ? const SizedBox() : SizedBox( height: _placesList.isNotEmpty ? 200 : 0, child: ListView.builder( itemCount: _placesList.length, itemBuilder: (context, index) { return ListTile( onTap: () { final selectedAddress = _placesList[index]['formatted_address']; final selectedLatitude = _placesList[index] ['geometry']['location']['lat']; final selectedLongitude = _placesList[index] ['geometry']['location']['lng']; _locacionController.text = selectedAddress; _animateCameraToPosition(LatLng( selectedLatitude, selectedLongitude, )); if (mounted) { setState(() { _placesList = []; }); } }, title: Text( _placesList[index]['formatted_address']), ); }, ), ), const SizedBox(height: 15), Row( children: [ Expanded( child: TextFormField( readOnly: true, decoration: InputDecoration( prefixIcon: const Icon(Icons.calendar_month), suffixIcon: _selectedDate == null ? const Icon(Icons.arrow_drop_down) : null, hintText: 'Fecha', ), controller: TextEditingController( text: _selectedDate == null ? '' : formatter.format(_selectedDate!), ), onTap: () { if (_profesionalController.text.isNotEmpty) { _selectDateAndTime(context); } else { WarningSnackbar.show( title: 'Selecciona un profesional', message: 'Selecciona un profesional antes de elegir la fecha y la hora de la cita.', backgroundColor: Colors.orange, ); } }, ), ), Expanded( child: TextFormField( onTap: () { if (_profesionalController.text.isNotEmpty) { _selectDateAndTime(context); } else { WarningSnackbar.show( title: 'Selecciona un profesional', message: 'Selecciona un profesional antes de elegir la fecha y la hora de la cita.', backgroundColor: Colors.orange); } }, readOnly: true, decoration: const InputDecoration( suffixIcon: Icon(Icons.arrow_drop_down), hintText: 'Hora', ), controller: TextEditingController( text: _selectedTime == null ? '' : ' ${_selectedTime!.format(context)}', ), )) ], ), const SizedBox(height: 15), TextFormField( controller: _observacionController, decoration: const InputDecoration( prefixIcon: Icon(Icons.message_outlined), hintText: 'Observaciones', ), ), const SizedBox(height: 20), Center( child: PrimaryButton( onPressed: () { if (user?.name == null || user?.name == '' || user?.phoneNumber == null || user?.phoneNumber == '') { WarningSnackbar.show( title: 'Completa tu perfil', message: 'Diligencia la información de tu perfil antes de solicitar un servicio.', ); Navigator.push( context, CupertinoPageRoute( builder: (BuildContext context) { return const ProfileScreen(); }, ), ); } else { try { final DateTime combinedDate = DateTime( _selectedDate!.year, _selectedDate!.month, _selectedDate!.day, _selectedTime!.hour, _selectedTime!.minute, ); DateTime time2 = combinedDate.add( const Duration(hours: 2), ); UserModel.getUser(uid.toString()).then((value) { eventoService .createEvent( value.name, _observacionController.text, '$_selectedDate', '$combinedDate', '$time2', professionalId, ubicacion, _locacionController.text, ubicacion != 'sitio' ? coordinates.latitude : professionalLatitude, ubicacion != 'sitio' ? coordinates.longitude : professionalLongitude, 'pendiente', professionalTarifa == 0 ? 0 : professionalTarifa, false, false, ) .then((value) { if (professionalToken != '') { sendPushNotification(professionalToken); } Navigator.pushReplacement( context, CupertinoPageRoute( builder: (BuildContext context) { return ServiceAfterScreen( eventoId: value, ); }, ), ); }); }); } catch (e) { WarningSnackbar.show( title: 'Llena todos los campos', message: 'Asegurate de llenar todos los campos antes de solicitar un servicio.', ); } } }, text: 'Solicitar servicio', minWidth: 230, ), ), ], ), ), ) ], ), ), ); } Future getLocationUpdates() async { bool _serviceEnabled; PermissionStatus _permissionGranted; _serviceEnabled = await _locationController.serviceEnabled(); if (_serviceEnabled) { _serviceEnabled = await _locationController.requestService(); } else { return; } _permissionGranted = await _locationController.hasPermission(); if (_permissionGranted == PermissionStatus.denied) { _permissionGranted = await _locationController.requestPermission(); if (_permissionGranted != PermissionStatus.granted) { return; } } _locationController.onLocationChanged .listen((LocationData currentLocation) { if (currentLocation.latitude != null && currentLocation.longitude != null) { if (mounted) { setState(() { _currentP = LatLng( currentLocation.latitude!, currentLocation.longitude!, ); }); } } }); } Future> getPolylinePoints( LatLng originP, LatLng destinationP) async { List polylineCoordinates = []; PolylinePoints polylinePoints = PolylinePoints(); PolylineResult result = await polylinePoints.getRouteBetweenCoordinates( GOOGLE_MAPS_API_KEY, PointLatLng(originP.latitude, originP.longitude), PointLatLng(destinationP.latitude, destinationP.longitude), travelMode: TravelMode.driving, ); if (result.points.isNotEmpty) { result.points.forEach((PointLatLng point) { polylineCoordinates.add(LatLng(point.latitude, point.longitude)); }); } else { print(result.errorMessage); } return polylineCoordinates; } void generatePolyLineFromPoints(List polylineCoordinates) async { PolylineId id = const PolylineId("poly"); Polyline polyline = Polyline( polylineId: id, color: Colors.blue, points: polylineCoordinates, width: 6, ); if (mounted) { setState(() { polylines[id] = polyline; }); } } void _applyMapStyle(GoogleMapController controller) { controller.setMapStyle(MAP_STYLE); } Future _animateCameraToPosition(LatLng position) async { final GoogleMapController controller = await _mapController.future; controller.animateCamera( CameraUpdate.newCameraPosition( CameraPosition( target: position, zoom: 17.5, ), ), ); } Future _animateInitialCameraToPosition(String coordsOfCity) async { List coords = coordsOfCity.split(','); double lat = double.parse(coords[0]); double lng = double.parse(coords[1]); _animateCameraToPosition(LatLng(lat, lng)); try { Position position = await _determinePosition(); _animateCameraToPosition(LatLng(position.latitude, position.longitude)); if (mounted) { setState(() {}); } } catch (e) { print('Error: $e'); } } void updateMarkersForServiceType(String serviceType) { getUsersWithActiveStatus(serviceType).then((value) { markers.clear(); for (var doc in value) { final element = doc.data()!; if (element['latitude'] != null && element['longitude'] != null) { markers.add( Marker( icon: _markerIcon!, markerId: MarkerId(doc.id), position: LatLng( element['latitude'], element['longitude'], ), ), ); } } }).catchError((e) { print('Error al actualizar los marcadores: $e'); }); } Future _determinePosition() async { bool serviceEnabled; LocationPermission permission; serviceEnabled = await Geolocator.isLocationServiceEnabled(); if (!serviceEnabled) { return Future.error('Location services are disabled'); } permission = await Geolocator.checkPermission(); if (permission == LocationPermission.denied) { permission = await Geolocator.requestPermission(); if (permission == LocationPermission.denied) { return Future.error('Location permission denied'); } } if (permission == LocationPermission.deniedForever) { return Future.error('Location permissions are permanently denied'); } Position position = await Geolocator.getCurrentPosition(); return position; } Future getLocationName(double latitude, double longitude) async { String address; try { List placemarks = await geocoding.placemarkFromCoordinates(latitude, longitude); geocoding.Placemark place = placemarks[0]; if (place.thoroughfare != '' || place.subThoroughfare != '') { address = "${place.thoroughfare} ${place.subThoroughfare} ${place.subLocality}, ${place.locality}, ${place.administrativeArea}"; } else { address = ''; } } catch (e) { address = ''; } return address; } void placeAutoComplete(String query) async { Uri uri = Uri.https("admin.prosapp.co", "/autocomplete", { "input": query, "location": _coordsOfCity, }); String? response = await NetworkUtility.fetchUrl(uri); if (response != null) { if (mounted) { setState(() { _placesList = jsonDecode(response.toString())['results']; }); } } } Future _selectDate(BuildContext context) async { final DateTime? picked = await showDatePicker( context: context, initialDate: now, firstDate: now, lastDate: DateTime(now.year + 1), ); if (picked != null && picked != _selectedDate) { if (mounted) { setState(() { _selectedDate = picked; }); } } } Future _selectTime(BuildContext context) async { final TimeOfDay? pickedTime = await showTimePicker( context: context, initialTime: TimeOfDay.now(), ); if (pickedTime != null) { if (mounted) { setState(() { _selectedTime = pickedTime; }); } } } Future>>> getUsersWithActiveStatus( String serviceType) async { dynamic querySnapshot; if (serviceType != "Servicio") { querySnapshot = await FirebaseFirestore.instance .collection('users') .where('estado', isEqualTo: 'activo') .where('profesion', isEqualTo: serviceType) .get(); } else { querySnapshot = await FirebaseFirestore.instance .collection('users') .where('estado', isEqualTo: 'activo') .get(); } return querySnapshot.docs; } void _saveToken() async { FirebaseMessaging messaging = FirebaseMessaging.instance; final token = await messaging.getToken(); try { await FirebaseFirestore.instance .collection('users') .doc(uid) .update({'token': token}); } catch (e) { print(e); } } Future _getAppVersion() async { PackageInfo packageInfo = await PackageInfo.fromPlatform(); String version = packageInfo.version; print('App version: $version'); if (mounted) { setState(() { appVersion = version; }); } print('App version: $settings'); if (settings != null) { _checkForUpdate(settings); } } void _checkForUpdate(String? settings) { if (appVersion == settings) { showDialog( context: context, builder: (context) { return WillPopScope( onWillPop: () async { return false; }, child: Center( child: AlertDialog( shape: RoundedRectangleBorder( borderRadius: BorderRadius.circular(16.0), ), title: const Text( "Debes Actualizar la Aplicación", style: TextStyle(fontWeight: FontWeight.bold), ), content: const Text( "Tienes una versión desactualizada de nuestra aplicación. Te recomendamos actualizarla para disfrutar de las últimas características y mejoras.", ), actions: [ TextButton( child: const Text( "Actualizar", style: TextStyle(fontWeight: FontWeight.w600, fontSize: 20), ), onPressed: () { if (Platform.isAndroid) { launchUrl(_url); } else { launchUrl(_urlIos); } }, ), ], ), ), ); }, ); } } Future _selectDateAndTime(BuildContext context) async { final DateTime? pickedDate = await showDatePicker( context: context, initialDate: now, firstDate: now, lastDate: DateTime(now.year + 1), ); if (pickedDate == null) { return; } final TimeOfDay? pickedTime = await showTimePicker( context: context, initialTime: TimeOfDay.now(), ); if (pickedTime != null) { final DateTime selectedDateTime = DateTime( pickedDate.year, pickedDate.month, pickedDate.day, pickedTime.hour, pickedTime.minute, ); if (selectedDateTime.isBefore(DateTime.now())) { WarningSnackbar.show( title: 'Hora inválida', message: 'La hora seleccionada no es valida porque es anterior a la hora actual.', ); return; } if (mounted) { setState(() { _selectedDate = pickedDate; _selectedTime = pickedTime; }); } } } Future sendPushNotification(String pro) async { try { http.Response response = await http.post( Uri.parse('https://fcm.googleapis.com/fcm/send'), headers: { 'Content-Type': 'application/json; charset=UTF-8', 'Authorization': 'key=AAAAORdR-xU:APA91bF_wblg86jHAC-uexrXPHavYRlk5wge1Gf46m56V4J2D2L37Cp_hf46JZUzpvsWPSpqc5ewHelKI9LTifUG_s2mciMI6e5VLKo7E1R8btbNo7iaM9do2ctoyHKUm1atlZBdKaN2', }, body: jsonEncode( { 'notification': { 'body': 'alguien a solicitado tus servicios', 'title': 'Nueva solicitud', }, 'priority': 'high', 'data': { 'click_action': 'FLUTTER_NOTIFICATION_CLICK', 'id': '1', 'status': 'done', 'screen': 'solicitud' }, 'to': pro }, ), ); response; } catch (e) { print('error al enviar notificacion $e'); } } }