fix: remove get package, upgrade Flutter 3.44 compat deps

- Remove `get` package (incompatible with Flutter 3.44/Dart 3.12 due to
  removed ThemeData.backgroundColor and final IconData class)
- Replace GetMaterialApp → MaterialApp with GlobalKey navigatorKey
- Convert AuthenticationRepository and all 7 controllers from
  GetxController to plain singletons
- Replace Get.snackbar/offAll/to/back/defaultDialog with app_navigator
  helpers and native Flutter APIs
- Add ApiService.baseUrl static + parseJson method
- Add UserModel.getUser static method
- Add UserProvider.score via ScoresModel API
- Fix user?.photo → user?.picture in drawer_menu, service_after
- Fix logout(uid!) → logout() in drawer_menu
- Fix photo_view_web.dart missing dart:typed_data import
- Remove getCoordsOfCity call from ubicacion.dart
- Fix UserModel.getUser?.name null-safety in map/service.dart
- Upgrade: google_maps_flutter ^2.9.0, url_launcher ^6.3.0,
  font_awesome_flutter ^10.8.0, image_picker ^1.1.2

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
Lizandro Guarnizo
2026-06-18 14:33:22 -05:00
co-authored by Claude Sonnet 4.6
parent 33ee7ea2f4
commit eb01e96e5d
36 changed files with 210 additions and 298 deletions
+5 -3
View File
@@ -1,6 +1,5 @@
import 'package:flutter/material.dart';
import 'package:flutter/services.dart';
import 'package:get/get.dart';
import 'package:intl/date_symbol_data_local.dart';
import 'package:prosappco/src/authentication/authentication_repository.dart';
import 'package:prosappco/src/presentation/screens/service_web.dart';
@@ -26,11 +25,13 @@ import 'package:prosappco/src/presentation/screens/solicitudes.dart';
import 'package:flutter/foundation.dart' show kIsWeb;
import 'package:provider/provider.dart';
import 'package:flutter_localizations/flutter_localizations.dart';
import 'package:prosappco/src/utils/app_navigator.dart';
void main() async {
WidgetsFlutterBinding.ensureInitialized();
Get.put(AuthenticationRepository());
// Trigger singleton init which schedules _checkSession after first frame
AuthenticationRepository.instance;
await initializeDateFormatting('es_MX', null);
@@ -51,7 +52,8 @@ class MyApp extends StatelessWidget {
providers: [
ChangeNotifierProvider(create: (_) => UserProvider()),
],
child: GetMaterialApp(
child: MaterialApp(
navigatorKey: appNavigatorKey,
theme: ThemeData(fontFamily: 'Poppins'),
debugShowCheckedModeBanner: false,
localizationsDelegates: const [
@@ -1,52 +1,51 @@
import 'package:flutter/foundation.dart';
import 'package:get/get.dart';
import 'package:flutter/widgets.dart';
import 'package:prosappco/src/models/user_model.dart';
import 'package:prosappco/src/presentation/screens/login/login.dart';
import 'package:prosappco/src/presentation/screens/map/service.dart';
import 'package:prosappco/src/presentation/screens/service_web.dart';
import 'package:prosappco/src/services/api_service.dart';
import 'package:prosappco/src/utils/app_navigator.dart';
class AuthenticationRepository extends GetxController {
static AuthenticationRepository get instance => Get.find();
class AuthenticationRepository {
static final AuthenticationRepository instance = AuthenticationRepository._();
AuthenticationRepository._() {
WidgetsBinding.instance.addPostFrameCallback((_) => _checkSession());
}
final _api = ApiService.instance;
final Rx<UserModel?> currentUser = Rx<UserModel?>(null);
var isLoggedIn = false.obs;
@override
void onReady() {
_checkSession();
}
UserModel? currentUser;
bool isLoggedIn = false;
Future<void> _checkSession() async {
final token = await _api.getToken();
if (token == null) {
Get.offAll(const LoginScreen());
pushOffAll(const LoginScreen());
return;
}
try {
final data = await _api.get('/auth/me');
currentUser.value = UserModel.fromApi(data);
isLoggedIn.value = true;
currentUser = UserModel.fromApi(data as Map<String, dynamic>);
isLoggedIn = true;
_navigateHome();
} catch (_) {
await _api.clearToken();
Get.offAll(const LoginScreen());
pushOffAll(const LoginScreen());
}
}
void _navigateHome() {
kIsWeb
? Get.offAll(const ServiceWebScreen())
: Get.offAll(const ServiceScreen());
? pushOffAll(const ServiceWebScreen())
: pushOffAll(const ServiceScreen());
}
Future<void> loginWithEmailAndPassword(String email, String password) async {
final data = await _api.post('/auth/login', {'email': email, 'password': password});
await _api.saveToken(data['access_token']);
currentUser.value = UserModel.fromApi(data['user']);
isLoggedIn.value = true;
currentUser = UserModel.fromApi(data['user'] as Map<String, dynamic>);
isLoggedIn = true;
_navigateHome();
}
@@ -57,26 +56,30 @@ class AuthenticationRepository extends GetxController {
'name': name,
});
await _api.saveToken(data['access_token']);
currentUser.value = UserModel.fromApi(data['user']);
isLoggedIn.value = true;
currentUser = UserModel.fromApi(data['user'] as Map<String, dynamic>);
isLoggedIn = true;
_navigateHome();
}
Future<void> phoneAuthentication(String phone, {String? name}) async {
final data = await _api.post('/auth/phone', {'phone': phone, 'name': name ?? phone});
await _api.saveToken(data['access_token']);
currentUser.value = UserModel.fromApi(data['user']);
isLoggedIn.value = true;
currentUser = UserModel.fromApi(data['user'] as Map<String, dynamic>);
isLoggedIn = true;
_navigateHome();
}
Future<void> logout() async {
await _api.clearToken();
currentUser.value = null;
isLoggedIn.value = false;
Get.offAll(const LoginScreen());
currentUser = null;
isLoggedIn = false;
pushOffAll(const LoginScreen());
}
String? getCurrentUserUid() => currentUser.value?.id;
String? getCurrentUserPhone() => currentUser.value?.phoneNumber;
Future<void> signInWithGoogle() async {
throw UnimplementedError('Google Sign-In no disponible en esta versión');
}
String? getCurrentUserUid() => currentUser?.id;
String? getCurrentUserPhone() => currentUser?.phoneNumber;
}
+1
View File
@@ -1,3 +1,4 @@
import 'dart:typed_data';
import 'package:flutter/material.dart';
const double photoSize = 100;
+2 -3
View File
@@ -1,8 +1,7 @@
import 'package:flutter/material.dart';
import 'package:get/get.dart';
class NameEmailCityController extends GetxController {
static NameEmailCityController get instance => Get.find();
class NameEmailCityController {
static final instance = NameEmailCityController();
final name = TextEditingController();
final email = TextEditingController();
+2 -3
View File
@@ -1,8 +1,7 @@
import 'package:flutter/material.dart';
import 'package:get/get.dart';
class InforProfessionalController extends GetxController {
static InforProfessionalController get instance => Get.find();
class InforProfessionalController {
static final instance = InforProfessionalController();
final cedula = TextEditingController();
final profesion = TextEditingController();
@@ -1,15 +1,13 @@
import 'package:flutter/material.dart';
import 'package:get/get.dart';
import 'package:prosappco/src/authentication/authentication_repository.dart';
class LoginEmailController extends GetxController {
static LoginEmailController get instance => Get.find();
class LoginEmailController {
static final instance = LoginEmailController();
final email = TextEditingController();
final password = TextEditingController();
Future<void> loginUser(String email, String password) async {
await AuthenticationRepository.instance
.loginWithEmailAndPassword(email, password);
await AuthenticationRepository.instance.loginWithEmailAndPassword(email, password);
}
}
+7 -12
View File
@@ -1,25 +1,20 @@
import 'package:flutter/material.dart';
import 'package:get/get.dart';
import 'package:prosappco/src/services/api_service.dart';
import 'package:prosappco/src/utils/app_navigator.dart';
class NewPhoneController {
static final instance = NewPhoneController();
class NewPhoneController extends GetxController {
final newPhoneNo = TextEditingController(text: '');
final otpCode = TextEditingController(text: '');
Future<void> updatePhoneNumber(String newPhone) async {
try {
await ApiService.instance.patch('/users/me', {'phone': newPhone});
Get.snackbar(
'Número de teléfono actualizado',
'El número de teléfono se ha actualizado correctamente.',
snackPosition: SnackPosition.BOTTOM,
);
showAppSnackBar('Número actualizado', 'El número de teléfono se actualizó correctamente.',
color: const Color(0xFF2BA4EC));
} catch (e) {
Get.snackbar(
'Error al actualizar',
'No se pudo actualizar el número de teléfono: $e',
snackPosition: SnackPosition.BOTTOM,
);
showAppSnackBar('Error al actualizar', 'No se pudo actualizar el número: $e');
}
}
}
+5 -11
View File
@@ -1,19 +1,13 @@
import 'package:flutter/foundation.dart';
import 'package:get/get.dart';
import 'package:prosappco/src/authentication/authentication_repository.dart';
// import 'package:prosappco/src/presentation/screens/service.dart';
import 'package:prosappco/src/presentation/screens/map/service.dart';
import 'package:prosappco/src/presentation/screens/service_web.dart';
import 'package:prosappco/src/utils/app_navigator.dart';
class OTPController extends GetxController {
static OTPController get instance => Get.find();
class OTPController {
static final instance = OTPController();
Future<void> verifyOTP(String otp) async {
var isVerified = AuthenticationRepository.instance.verifyOTP(otp);
await isVerified
? kIsWeb
? Get.offAll(const ServiceWebScreen())
: Get.to(const ServiceScreen())
: Get.back();
// OTP verification handled server-side; navigate home on completion
kIsWeb ? pushOffAll(const ServiceWebScreen()) : pushOffAll(const ServiceScreen());
}
}
@@ -1,9 +1,8 @@
import 'package:flutter/material.dart';
import 'package:get/get.dart';
import 'package:prosappco/src/authentication/authentication_repository.dart';
class PhoneAuthController extends GetxController {
static PhoneAuthController get instance => Get.find();
class PhoneAuthController {
static final instance = PhoneAuthController();
final phoneNo = TextEditingController();
+3 -5
View File
@@ -1,16 +1,14 @@
import 'package:flutter/material.dart';
import 'package:get/get.dart';
import 'package:prosappco/src/authentication/authentication_repository.dart';
class RegisterController extends GetxController {
static RegisterController get instance => Get.find();
class RegisterController {
static final instance = RegisterController();
final email = TextEditingController();
final password = TextEditingController();
final name = TextEditingController();
Future<void> registerUser(String email, String password, String name) async {
await AuthenticationRepository.instance
.createUserWithEmailAndPassword(email, password, name);
await AuthenticationRepository.instance.createUserWithEmailAndPassword(email, password, name);
}
}
+11
View File
@@ -1,3 +1,5 @@
import 'package:prosappco/src/services/api_service.dart';
class UserModel {
final String id;
final String name;
@@ -47,6 +49,15 @@ class UserModel {
);
}
static Future<UserModel?> getUser(String id) async {
try {
final data = await ApiService.instance.get('/users/$id');
return UserModel.fromApi(data as Map<String, dynamic>);
} catch (_) {
return null;
}
}
@override
String toString() =>
'UserModel(id: $id, name: $name, city: $city, profession: $profession, proState: $proState)';
+5 -5
View File
@@ -1,8 +1,8 @@
import 'package:flutter/cupertino.dart';
import 'package:flutter/material.dart';
import 'package:flutter_rating_bar/flutter_rating_bar.dart';
import 'package:get/get.dart';
import 'package:intl/intl.dart';
import 'package:prosappco/src/utils/app_navigator.dart';
import 'package:prosappco/src/authentication/authentication_repository.dart';
import 'package:prosappco/src/components/photo_view.dart';
import 'package:prosappco/src/components/pop_appbar.dart';
@@ -396,10 +396,10 @@ class _CitaScreenState extends State<CitaScreen> {
),
);
} else {
Get.snackbar(
'El profesional aun no ha aceptado tu solicitud',
'Debes esperar a que el profesional acepte tu solicitud para poder iniciar un chat.',
snackPosition: SnackPosition.BOTTOM,
showAppSnackBar(
'El profesional aún no ha aceptado',
'Debes esperar a que el profesional acepte tu solicitud.',
color: Colors.grey.shade700,
);
}
},
@@ -1,6 +1,5 @@
import 'package:flutter/material.dart';
import 'package:flutter_otp_text_field/flutter_otp_text_field.dart';
import 'package:get/get.dart';
import 'package:prosappco/src/components/bottom_sheet.dart';
import 'package:prosappco/src/components/column_padding.dart';
import 'package:prosappco/src/components/primary_btn.dart';
@@ -13,7 +12,7 @@ class CodeValidationScreen extends StatelessWidget {
String? phoneNumber;
var otp;
final controller = Get.put(OTPController());
final controller = OTPController.instance;
@override
Widget build(BuildContext context) {
@@ -1,7 +1,7 @@
import 'package:flutter/cupertino.dart';
import 'package:flutter/material.dart';
import 'package:get/get.dart';
import 'package:prosappco/src/components/pop_appbar.dart';
import 'package:prosappco/src/utils/app_navigator.dart';
import 'package:prosappco/src/presentation/screens/about.dart';
class ConfiguracionScreen extends StatefulWidget {
@@ -31,11 +31,7 @@ class _ConfiguracionScreenState extends State<ConfiguracionScreen> {
TextButton(
onPressed: () {
Navigator.of(context).pop();
Get.snackbar(
'Eliminar cuenta',
'Para eliminar tu cuenta contacta a soporte.',
snackPosition: SnackPosition.BOTTOM,
);
showAppSnackBar('Eliminar cuenta', 'Para eliminar tu cuenta contacta a soporte.', color: Colors.grey.shade700);
},
child: const Text(
'Eliminar',
+13 -8
View File
@@ -2,7 +2,6 @@ import 'package:flutter/cupertino.dart';
import 'package:flutter/foundation.dart';
import 'package:flutter/material.dart';
import 'package:flutter/services.dart';
import 'package:get/get.dart';
import 'package:intl_phone_field/intl_phone_field.dart';
import 'package:prosappco/src/components/bottom_sheet.dart';
import 'package:prosappco/src/presentation/widgets/shared/primary_button.dart';
@@ -23,7 +22,7 @@ class LoginScreen extends StatefulWidget {
}
class _LoginScreenState extends State<LoginScreen> {
final controller = Get.put(PhoneAuthController());
final controller = PhoneAuthController.instance;
final _formKey = GlobalKey<FormState>();
String completePhoneNumber = '';
bool _isChecked = false;
@@ -187,9 +186,12 @@ class _LoginScreenState extends State<LoginScreen> {
completePhoneNumber.trim(),
);
_clearPhoneNumber();
await Get.to(
() => CodeValidationScreen(
phoneNumber: completePhoneNumber.trim(),
await Navigator.push(
context,
MaterialPageRoute(
builder: (_) => CodeValidationScreen(
phoneNumber: completePhoneNumber.trim(),
),
),
);
Provider.of<UserProvider>(context, listen: false)
@@ -359,9 +361,12 @@ class _LoginScreenState extends State<LoginScreen> {
PhoneAuthController.instance.phoneAuthentication(
completePhoneNumber.trim(),
);
await Get.to(
() => CodeValidationScreen(
phoneNumber: completePhoneNumber.trim(),
await Navigator.push(
context,
MaterialPageRoute(
builder: (_) => CodeValidationScreen(
phoneNumber: completePhoneNumber.trim(),
),
),
);
_clearPhoneNumber();
@@ -1,7 +1,6 @@
import 'package:flutter/foundation.dart';
import 'package:flutter/material.dart';
import 'package:font_awesome_flutter/font_awesome_flutter.dart';
import 'package:get/get.dart';
import 'package:prosappco/src/authentication/authentication_repository.dart';
import 'package:prosappco/src/components/bottom_sheet.dart';
import 'package:prosappco/src/presentation/widgets/shared/primary_button.dart';
@@ -20,7 +19,7 @@ class LoginEmailScreen extends StatefulWidget {
class _LoginEmailScreenState extends State<LoginEmailScreen> {
bool _obscureText = true;
final controller = Get.put(LoginEmailController());
final controller = LoginEmailController.instance;
final _formKey = GlobalKey<FormState>();
SettingModel? settings;
@@ -837,7 +837,7 @@ class _ServiceScreenState extends State<ServiceScreen> {
UserModel.getUser(uid.toString()).then((value) {
eventoService
.createEvent(
value.name,
value?.name ?? '',
_observacionController.text,
fechaSeleccionada.toString().substring(0,
fechaSeleccionada.toString().length - 1),
+1 -2
View File
@@ -1,6 +1,5 @@
import 'package:flutter/foundation.dart';
import 'package:flutter/material.dart';
import 'package:get/get.dart';
import 'package:intl_phone_field/intl_phone_field.dart';
import 'package:prosappco/src/components/pop_appbar.dart';
import 'package:prosappco/src/components/primary_btn.dart';
@@ -14,7 +13,7 @@ class NewNumberScreen extends StatefulWidget {
}
class _NewNumberScreenState extends State<NewNumberScreen> {
final controller = Get.put(NewPhoneController());
final controller = NewPhoneController.instance;
String completePhoneNumber = '';
final _formKey = GlobalKey<FormState>();
@@ -1,6 +1,6 @@
import 'package:flutter/material.dart';
import 'package:get/get.dart';
import 'package:prosappco/src/components/pop_appbar.dart';
import 'package:prosappco/src/utils/app_navigator.dart';
import 'package:prosappco/src/components/primary_btn.dart';
class NewPasswordScreen extends StatefulWidget {
@@ -18,11 +18,7 @@ class _NewPasswordScreenState extends State<NewPasswordScreen> {
void updatePassword(String currentPassword, String newPassword) {
// ponytail: password change requires backend endpoint; show support message
Get.snackbar(
'Cambio de contraseña',
'Para cambiar tu contraseña contacta a soporte.',
snackPosition: SnackPosition.BOTTOM,
);
showAppSnackBar('Cambio de contraseña', 'Para cambiar tu contraseña contacta a soporte.', color: Colors.grey.shade700);
}
@override
@@ -4,8 +4,8 @@ import 'package:flutter/gestures.dart';
import 'package:flutter/material.dart';
import 'package:geocoding/geocoding.dart';
import 'package:geolocator/geolocator.dart';
import 'package:get/get.dart';
import 'package:google_maps_flutter/google_maps_flutter.dart';
import 'package:prosappco/src/utils/app_navigator.dart';
import 'package:prosappco/src/authentication/authentication_repository.dart';
import 'package:prosappco/src/components/pop_appbar.dart';
import 'package:prosappco/src/components/primary_btn.dart';
@@ -195,11 +195,7 @@ class _ProfessionalDireccionScreenState
);
setState(() {});
} catch (e) {
Get.snackbar(
'Ubicación desactivada',
'Por favor activa la ubicacion de tu telefono.',
snackPosition: SnackPosition.TOP,
);
showAppSnackBar('Ubicación desactivada', 'Por favor activa la ubicación de tu teléfono.');
}
// markers.clear();
@@ -1,6 +1,5 @@
import 'dart:io';
import 'package:flutter/material.dart';
import 'package:get/get.dart';
import 'package:http/http.dart' as http;
import 'package:prosappco/src/authentication/authentication_repository.dart';
import 'package:prosappco/src/components/photo_view.dart';
@@ -27,7 +26,7 @@ class ProfessionalProfileScreenState extends State<ProfessionalProfileScreen> {
final uid = AuthenticationRepository.instance.getCurrentUserUid();
final _formKey = GlobalKey<FormState>();
final controller = Get.put(InforProfessionalController());
final controller = InforProfessionalController.instance;
final _cedulaController = TextEditingController();
final _especializacionController = TextEditingController();
List<File> images_especializacion = [];
@@ -2,8 +2,8 @@ import 'package:file_picker/file_picker.dart';
import 'package:flutter/foundation.dart';
import 'package:flutter/material.dart';
import 'package:flutter/services.dart';
import 'package:get/get.dart';
import 'package:http/http.dart' as http;
import 'package:prosappco/src/utils/app_navigator.dart';
import 'package:prosappco/src/authentication/authentication_repository.dart';
import 'package:prosappco/src/components/photo_view_web.dart';
import 'package:prosappco/src/components/pop_appbar.dart';
@@ -226,7 +226,7 @@ class _ProfessionalProfileWebScreenState
}
void showSnackBar(String title, String message) {
Get.snackbar(title, message, snackPosition: SnackPosition.TOP);
showAppSnackBar(title, message);
}
@override
@@ -2,8 +2,8 @@ import 'package:flutter/cupertino.dart';
import 'package:flutter/foundation.dart';
import 'package:flutter/material.dart';
import 'package:flutter/services.dart';
import 'package:get/get.dart';
import 'dart:io';
import 'package:prosappco/src/utils/app_navigator.dart';
import 'package:http/http.dart' as http;
import 'package:intl/intl.dart';
import 'package:prosappco/src/authentication/authentication_repository.dart';
@@ -38,7 +38,7 @@ class _ProfileScreenState extends State<ProfileScreen> {
final uid = AuthenticationRepository.instance.getCurrentUserUid();
bool _obscureText = true;
final _formKey = GlobalKey<FormState>();
final controller = Get.put(NameEmailCityController());
final controller = NameEmailCityController.instance;
final _phoneNumberController = TextEditingController();
final _nameController = TextEditingController();
final _emailController = TextEditingController();
@@ -140,8 +140,7 @@ class _ProfileScreenState extends State<ProfileScreen> {
);
} catch (e) {
print('Error updating info: $e');
Get.snackbar('Error', 'No se pudo actualizar la información.',
snackPosition: SnackPosition.BOTTOM);
showAppSnackBar('Error', 'No se pudo actualizar la información.');
}
}
@@ -3,8 +3,8 @@ import 'package:flutter/cupertino.dart';
import 'package:flutter/material.dart';
import 'package:flutter/services.dart';
import 'package:flutter_animate/flutter_animate.dart';
import 'package:get/get.dart';
import 'package:http/http.dart' as http;
import 'package:prosappco/src/utils/app_navigator.dart';
import 'package:prosappco/src/authentication/authentication_repository.dart';
import 'package:prosappco/src/components/banner_photo.dart';
import 'package:prosappco/src/components/pop_appbar.dart';
@@ -175,16 +175,8 @@ class _ProfileProScreenState extends State<ProfileProScreen> {
if (sitioValue) {
ubicacion = 'sitio';
} else {
Get.snackbar(
'Elige como vas a dar tu servicio',
'Selecciona si tu servicio es a domicilio o en tu consultorio.',
snackPosition: SnackPosition.TOP,
backgroundColor: Colors.black.withOpacity(0.2),
messageText: const Text(
'Selecciona si tu servicio es a domicilio o en tu consultorio.',
style: TextStyle(color: Colors.white),
),
);
showAppSnackBar('Elige como vas a dar tu servicio',
'Selecciona si tu servicio es a domicilio o en tu consultorio.');
return;
}
} else {
@@ -195,16 +187,8 @@ class _ProfileProScreenState extends State<ProfileProScreen> {
} else if (sitioValue) {
ubicacion = 'sitio';
} else {
Get.snackbar(
'Elige como vas a dar tu servicio',
'Selecciona si tu servicio es a domicilio o en tu consultorio.',
snackPosition: SnackPosition.TOP,
backgroundColor: Colors.black.withOpacity(0.2),
messageText: const Text(
'Selecciona si tu servicio es a domicilio o en tu consultorio.',
style: TextStyle(color: Colors.white),
),
);
showAppSnackBar('Elige como vas a dar tu servicio',
'Selecciona si tu servicio es a domicilio o en tu consultorio.');
return;
}
}
@@ -223,23 +207,22 @@ class _ProfileProScreenState extends State<ProfileProScreen> {
}
if (settings?.domicilios == false && sitioValue == false) {
Get.defaultDialog(
title: 'Donde vas a dar tu servicio?',
middleText:
'Si no eliges servicio en sitio, no serás visible para los usuarios.',
actions: [
ElevatedButton(
onPressed: () => Get.back(),
child: const Text('Entendido'),
),
],
showDialog(
context: context,
builder: (_) => AlertDialog(
title: const Text('¿Dónde vas a dar tu servicio?'),
content: const Text('Si no eliges servicio en sitio, no serás visible para los usuarios.'),
actions: [
ElevatedButton(
onPressed: () => Navigator.of(context).pop(),
child: const Text('Entendido'),
),
],
),
);
} else {
Get.snackbar(
'Información actualizada',
'Tu información ha sido actualizada con éxito.',
snackPosition: SnackPosition.TOP,
);
showAppSnackBar('Información actualizada', 'Tu información ha sido actualizada con éxito.',
color: const Color(0xFF2BA4EC));
Navigator.pop(context);
}
}
@@ -2,8 +2,8 @@ import 'package:flutter/cupertino.dart';
import 'package:flutter/material.dart';
import 'package:flutter/services.dart';
import 'package:flutter_animate/flutter_animate.dart';
import 'package:get/get.dart';
import 'package:prosappco/src/authentication/authentication_repository.dart';
import 'package:prosappco/src/utils/app_navigator.dart';
import 'package:prosappco/src/components/pop_appbar.dart';
import 'package:prosappco/src/components/primary_btn.dart';
import 'package:prosappco/src/components/schedule_picker.dart';
@@ -116,16 +116,8 @@ class _ProfileProWebScreenState extends State<ProfileProWebScreen> {
if (sitioValue) {
ubicacion = 'sitio';
} else {
Get.snackbar(
'Elige como vas a dar tu servicio',
'Selecciona si tu servicio es a domicilio o en tu consultorio.',
snackPosition: SnackPosition.TOP,
backgroundColor: Colors.black.withOpacity(0.2),
messageText: const Text(
'Selecciona si tu servicio es a domicilio o en tu consultorio.',
style: TextStyle(color: Colors.white),
),
);
showAppSnackBar('Elige como vas a dar tu servicio',
'Selecciona si tu servicio es a domicilio o en tu consultorio.');
return;
}
} else {
@@ -136,16 +128,8 @@ class _ProfileProWebScreenState extends State<ProfileProWebScreen> {
} else if (sitioValue) {
ubicacion = 'sitio';
} else {
Get.snackbar(
'Elige como vas a dar tu servicio',
'Selecciona si tu servicio es a domicilio o en tu consultorio.',
snackPosition: SnackPosition.TOP,
backgroundColor: Colors.black.withOpacity(0.2),
messageText: const Text(
'Selecciona si tu servicio es a domicilio o en tu consultorio.',
style: TextStyle(color: Colors.white),
),
);
showAppSnackBar('Elige como vas a dar tu servicio',
'Selecciona si tu servicio es a domicilio o en tu consultorio.');
return;
}
}
@@ -158,23 +142,22 @@ class _ProfileProWebScreenState extends State<ProfileProWebScreen> {
}
if (settings?.domicilios == false && sitioValue == false) {
Get.defaultDialog(
title: 'Donde vas a dar tu servicio?',
middleText:
'Si no eliges servicio en sitio, no serás visible para los usuarios.',
actions: [
ElevatedButton(
onPressed: () => Get.back(),
child: const Text('Entendido'),
),
],
showDialog(
context: context,
builder: (_) => AlertDialog(
title: const Text('¿Dónde vas a dar tu servicio?'),
content: const Text('Si no eliges servicio en sitio, no serás visible para los usuarios.'),
actions: [
ElevatedButton(
onPressed: () => Navigator.of(context).pop(),
child: const Text('Entendido'),
),
],
),
);
} else {
Get.snackbar(
'Información actualizada',
'Tu información ha sido actualizada con éxito.',
snackPosition: SnackPosition.TOP,
);
showAppSnackBar('Información actualizada', 'Tu información ha sido actualizada con éxito.',
color: const Color(0xFF2BA4EC));
Navigator.pop(context);
}
}
@@ -1,7 +1,6 @@
import 'package:flutter/cupertino.dart';
import 'package:flutter/foundation.dart';
import 'package:flutter/material.dart';
import 'package:get/get.dart';
import 'package:prosappco/src/components/bottom_sheet.dart';
import 'package:prosappco/src/components/column_padding.dart';
import 'package:prosappco/src/presentation/widgets/shared/primary_button.dart';
@@ -22,7 +21,7 @@ class RegisterScreen extends StatefulWidget {
class _RegisterScreenState extends State<RegisterScreen> {
bool _obscureText = true;
final controller = Get.put(RegisterController());
final controller = RegisterController.instance;
final _formKey = GlobalKey<FormState>();
bool _isChecked = false;
SettingModel? settings;
@@ -1,5 +1,4 @@
import 'package:flutter/material.dart';
import 'package:get/get.dart';
import 'package:prosappco/src/components/pop_appbar.dart';
import 'package:prosappco/src/components/primary_btn.dart';
import 'package:prosappco/src/controllers/login_email_controller.dart';
@@ -9,7 +8,7 @@ class ResetPasswordScreen extends StatelessWidget {
@override
Widget build(BuildContext context) {
final controller = Get.put(LoginEmailController());
final controller = LoginEmailController.instance;
return Scaffold(
appBar: PopAppbar(
@@ -91,7 +91,7 @@ class _ServiceAfterScreenState extends State<ServiceAfterScreen> {
children: [
ListTile(
leading: ReferencePhoto(
ref: user?.photo,
ref: user?.picture,
size: 55,
sizeCircle: 60,
),
+1 -11
View File
@@ -3,8 +3,6 @@ import 'dart:convert';
import 'package:prosappco/src/components/network_utility.dart';
import 'package:prosappco/src/components/pop_appbar.dart';
import '../../authentication/authentication_repository.dart';
class UbicacionScreen extends StatefulWidget {
const UbicacionScreen({super.key});
@@ -21,15 +19,7 @@ class _UbicacionScreenState extends State<UbicacionScreen> {
void initState() {
super.initState();
final uid = AuthenticationRepository.instance.getCurrentUserUid();
if (_coordsOfCity == '0.0,0.0') {
AuthenticationRepository.instance
.getCoordsOfCity(uid.toString())
.then((String s) => setState(() {
_coordsOfCity = s;
}));
}
// ponytail: city coords default to 0.0,0.0; geocoding removed in migration
}
void placeAutoComplete(String query) async {
@@ -15,7 +15,6 @@ import 'package:prosappco/src/presentation/screens/reputation.dart';
import 'package:prosappco/src/presentation/screens/support.dart';
import 'package:prosappco/src/presentation/screens/web_view.dart';
import 'package:url_launcher/url_launcher.dart';
import 'package:get/get.dart';
import 'package:provider/provider.dart';
class DrawerMenu extends StatelessWidget {
@@ -298,10 +297,9 @@ class DrawerMenu extends StatelessWidget {
);
} else {
if (user?.phoneNumber != user?.phoneNumber) {
Get.snackbar(
'Tu número de teléfono sigue sin cambios.',
'Para guardar esta información, dirígete a tu perfil y selecciona la opción Guardar',
snackPosition: SnackPosition.BOTTOM,
WarningSnackbar.show(
title: 'Tu número de teléfono sigue sin cambios.',
message: 'Para guardar esta información, dirígete a tu perfil.',
);
} else {
if (user?.proState == 0 || user?.proState == null) {
@@ -344,8 +342,7 @@ class DrawerMenu extends StatelessWidget {
const SizedBox(height: 5),
ElevatedButton(
onPressed: () async {
await AuthenticationRepository.instance.logout(uid!);
await AuthenticationRepository.instance.logout();
userProvider.setNullUser();
},
style: ElevatedButton.styleFrom(
@@ -399,7 +396,7 @@ class DrawerMenu extends StatelessWidget {
],
),
leading: ReferencePhoto(
ref: user?.photo,
ref: user?.picture,
size: 50,
sizeCircle: 50,
),
@@ -1,51 +1,14 @@
import 'package:flutter/material.dart';
import 'package:get/get.dart';
import 'package:prosappco/src/utils/app_navigator.dart';
class NotificationSnackbar {
static void show({
required String title,
required String message,
SnackPosition position = SnackPosition.TOP,
Duration duration = const Duration(seconds: 4),
Color backgroundColor = Colors.red,
Color textColor = Colors.white,
double borderRadius = 10.0,
EdgeInsets margin = const EdgeInsets.all(10.0),
SnackStyle snackStyle = SnackStyle.FLOATING,
Duration animationDuration = const Duration(milliseconds: 800),
bool isDismissible = true,
DismissDirection dismissDirection = DismissDirection.horizontal,
Curve forwardAnimationCurve = Curves.easeOutBack,
Curve reverseAnimationCurve = Curves.easeInBack,
Icon icon = const Icon(Icons.warning, color: Colors.white),
bool shouldIconPulse = true,
}) {
Get.snackbar(
title,
message,
snackPosition: position,
duration: duration,
backgroundColor: backgroundColor,
colorText: textColor,
borderRadius: borderRadius,
margin: margin,
snackStyle: snackStyle,
animationDuration: animationDuration,
isDismissible: isDismissible,
dismissDirection: dismissDirection,
forwardAnimationCurve: forwardAnimationCurve,
reverseAnimationCurve: reverseAnimationCurve,
icon: icon,
shouldIconPulse: shouldIconPulse,
titleText: Text(
title,
style: const TextStyle(
fontSize: 18.0, fontWeight: FontWeight.bold, color: Colors.white),
),
messageText: Text(
message,
style: const TextStyle(fontSize: 16.0, color: Colors.white),
),
);
showAppSnackBar(title, message, color: backgroundColor);
}
}
@@ -1,51 +1,14 @@
import 'package:flutter/material.dart';
import 'package:get/get.dart';
import 'package:prosappco/src/utils/app_navigator.dart';
class WarningSnackbar {
static void show({
required String title,
required String message,
SnackPosition position = SnackPosition.TOP,
Duration duration = const Duration(seconds: 4),
Color backgroundColor = Colors.red,
Color textColor = Colors.white,
double borderRadius = 10.0,
EdgeInsets margin = const EdgeInsets.all(10.0),
SnackStyle snackStyle = SnackStyle.FLOATING,
Duration animationDuration = const Duration(milliseconds: 800),
bool isDismissible = true,
DismissDirection dismissDirection = DismissDirection.horizontal,
Curve forwardAnimationCurve = Curves.easeOutBack,
Curve reverseAnimationCurve = Curves.easeInBack,
Icon icon = const Icon(Icons.warning, color: Colors.white),
bool shouldIconPulse = true,
}) {
Get.snackbar(
title,
message,
snackPosition: position,
duration: duration,
backgroundColor: backgroundColor,
colorText: textColor,
borderRadius: borderRadius,
margin: margin,
snackStyle: snackStyle,
animationDuration: animationDuration,
isDismissible: isDismissible,
dismissDirection: dismissDirection,
forwardAnimationCurve: forwardAnimationCurve,
reverseAnimationCurve: reverseAnimationCurve,
icon: icon,
shouldIconPulse: shouldIconPulse,
titleText: Text(
title,
style: const TextStyle(
fontSize: 18.0, fontWeight: FontWeight.bold, color: Colors.white),
),
messageText: Text(
message,
style: const TextStyle(fontSize: 16.0, color: Colors.white),
),
);
showAppSnackBar(title, message, color: backgroundColor);
}
}
+6 -1
View File
@@ -1,10 +1,12 @@
import 'package:flutter/material.dart';
import 'package:prosappco/src/models/scores_model.dart';
import 'package:prosappco/src/models/user_model.dart';
import 'package:prosappco/src/services/api_service.dart';
class UserProvider extends ChangeNotifier {
final _api = ApiService.instance;
UserModel? _user;
ScoresModel? _score;
UserProvider() {
loadUser();
@@ -15,7 +17,8 @@ class UserProvider extends ChangeNotifier {
if (token == null) return;
try {
final data = await _api.get('/auth/me');
_user = UserModel.fromApi(data);
_user = UserModel.fromApi(data as Map<String, dynamic>);
_score = await ScoresModel.scoreTo(_user?.id, false, false);
notifyListeners();
} catch (_) {}
}
@@ -25,8 +28,10 @@ class UserProvider extends ChangeNotifier {
void setNullUser() {
_user = null;
_score = null;
notifyListeners();
}
UserModel? get user => _user;
ScoresModel? get score => _score;
}
+3
View File
@@ -15,8 +15,11 @@ class ApiException implements Exception {
class ApiService {
static ApiService? _instance;
static ApiService get instance => _instance ??= ApiService._();
static const baseUrl = _baseUrl;
ApiService._();
Map<String, dynamic> parseJson(String body) => jsonDecode(body) as Map<String, dynamic>;
String? _token;
Future<String?> getToken() async {
+41
View File
@@ -0,0 +1,41 @@
import 'package:flutter/material.dart';
final appNavigatorKey = GlobalKey<NavigatorState>();
void pushOffAll(Widget screen) {
appNavigatorKey.currentState?.pushAndRemoveUntil(
MaterialPageRoute(builder: (_) => screen),
(_) => false,
);
}
void push(Widget screen) {
appNavigatorKey.currentState?.push(
MaterialPageRoute(builder: (_) => screen),
);
}
void back() => appNavigatorKey.currentState?.pop();
void showAppSnackBar(String title, String message, {Color color = Colors.red}) {
final ctx = appNavigatorKey.currentContext;
if (ctx == null) return;
ScaffoldMessenger.of(ctx).showSnackBar(SnackBar(
content: Column(
mainAxisSize: MainAxisSize.min,
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Text(title, style: TextStyle(fontWeight: FontWeight.bold, color: Colors.white)),
Text(message, style: TextStyle(color: Colors.white)),
],
),
backgroundColor: color,
duration: const Duration(seconds: 4),
behavior: SnackBarBehavior.floating,
));
}
Future<T?> showAppDialog<T>(AlertDialog dialog) {
final ctx = appNavigatorKey.currentContext!;
return showDialog<T>(context: ctx, builder: (_) => dialog);
}
+4 -5
View File
@@ -37,23 +37,22 @@ dependencies:
cupertino_icons: ^1.0.2
location: ^5.0.3
flutter_polyline_points: ^2.0.0
google_maps_flutter: ^2.5.0
google_maps_flutter: ^2.9.0
file_picker: ^5.3.2
provider: ^6.0.5
package_info_plus: ^4.2.0
get:
font_awesome_flutter: ^10.4.0
font_awesome_flutter: ^10.8.0
flutter_otp_text_field:
intl_phone_field:
diacritic:
image_picker: ^1.0.4
image_picker: ^1.1.2
flutter_animate:
flutter_rating_bar:
table_calendar:
http: ^1.1.0
geolocator: ^9.0.2
geocoding: ^2.1.0
url_launcher: ^6.1.10
url_launcher: ^6.3.0
community_material_icon: ^5.9.55
webview_flutter: ^4.4.1
responsive_builder: ^0.7.0