feat: migrate prosapp_web_app from Firebase to NestJS API
- Replace Firebase Auth with JWT stored in SharedPreferences - Replace Firestore with REST API calls via new ApiService - Replace Firebase Storage with POST /storage/upload - Remove firebase_auth, firebase_core, cloud_firestore, firebase_storage, firebase_messaging, google_sign_in deps - Add api_service.dart as central HTTP client Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
co-authored by
Claude Sonnet 4.6
parent
1c20d0c0cd
commit
b95e0630de
@@ -1,89 +1,2 @@
|
|||||||
// File generated by FlutterFire CLI.
|
// ponytail: Firebase removed; this file is kept as an empty stub so any lingering references compile.
|
||||||
// ignore_for_file: lines_longer_than_80_chars, avoid_classes_with_only_static_members
|
// No imports, no exports needed.
|
||||||
import 'package:firebase_core/firebase_core.dart' show FirebaseOptions;
|
|
||||||
import 'package:flutter/foundation.dart'
|
|
||||||
show defaultTargetPlatform, kIsWeb, TargetPlatform;
|
|
||||||
|
|
||||||
/// Default [FirebaseOptions] for use with your Firebase apps.
|
|
||||||
///
|
|
||||||
/// Example:
|
|
||||||
/// ```dart
|
|
||||||
/// import 'firebase_options.dart';
|
|
||||||
/// // ...
|
|
||||||
/// await Firebase.initializeApp(
|
|
||||||
/// options: DefaultFirebaseOptions.currentPlatform,
|
|
||||||
/// );
|
|
||||||
/// ```
|
|
||||||
class DefaultFirebaseOptions {
|
|
||||||
static FirebaseOptions get currentPlatform {
|
|
||||||
if (kIsWeb) {
|
|
||||||
return web;
|
|
||||||
}
|
|
||||||
switch (defaultTargetPlatform) {
|
|
||||||
case TargetPlatform.android:
|
|
||||||
return android;
|
|
||||||
case TargetPlatform.iOS:
|
|
||||||
return ios;
|
|
||||||
case TargetPlatform.macOS:
|
|
||||||
return macos;
|
|
||||||
case TargetPlatform.windows:
|
|
||||||
throw UnsupportedError(
|
|
||||||
'DefaultFirebaseOptions have not been configured for windows - '
|
|
||||||
'you can reconfigure this by running the FlutterFire CLI again.',
|
|
||||||
);
|
|
||||||
case TargetPlatform.linux:
|
|
||||||
throw UnsupportedError(
|
|
||||||
'DefaultFirebaseOptions have not been configured for linux - '
|
|
||||||
'you can reconfigure this by running the FlutterFire CLI again.',
|
|
||||||
);
|
|
||||||
default:
|
|
||||||
throw UnsupportedError(
|
|
||||||
'DefaultFirebaseOptions are not supported for this platform.',
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
static const FirebaseOptions web = FirebaseOptions(
|
|
||||||
apiKey: 'AIzaSyCNpUV_4cMEL9QZx7NESXK7QAlRjRGwx_Y',
|
|
||||||
appId: '1:245204384533:web:1b8f0b4c1d9ae21d9f871c',
|
|
||||||
messagingSenderId: '245204384533',
|
|
||||||
projectId: 'prosapp-5747a',
|
|
||||||
authDomain: 'prosapp-5747a.firebaseapp.com',
|
|
||||||
databaseURL: 'https://prosapp-5747a-default-rtdb.firebaseio.com',
|
|
||||||
storageBucket: 'prosapp-5747a.appspot.com',
|
|
||||||
measurementId: 'G-EVBEQRZEDY',
|
|
||||||
);
|
|
||||||
|
|
||||||
static const FirebaseOptions android = FirebaseOptions(
|
|
||||||
apiKey: 'AIzaSyBp7vsJvD6oXz0FORBLEeIELwId4a2onHQ',
|
|
||||||
appId: '1:245204384533:android:e01772831d86f5c79f871c',
|
|
||||||
messagingSenderId: '245204384533',
|
|
||||||
projectId: 'prosapp-5747a',
|
|
||||||
databaseURL: 'https://prosapp-5747a-default-rtdb.firebaseio.com',
|
|
||||||
storageBucket: 'prosapp-5747a.appspot.com',
|
|
||||||
);
|
|
||||||
|
|
||||||
static const FirebaseOptions ios = FirebaseOptions(
|
|
||||||
apiKey: 'AIzaSyDUhnkNkeEPkqcwVo5EWr63q3oEX6bDGic',
|
|
||||||
appId: '1:245204384533:ios:1d58f5624d0df4859f871c',
|
|
||||||
messagingSenderId: '245204384533',
|
|
||||||
projectId: 'prosapp-5747a',
|
|
||||||
databaseURL: 'https://prosapp-5747a-default-rtdb.firebaseio.com',
|
|
||||||
storageBucket: 'prosapp-5747a.appspot.com',
|
|
||||||
androidClientId: '245204384533-04oabs744hp8fjbdreijp4h16nvfmeti.apps.googleusercontent.com',
|
|
||||||
iosClientId: '245204384533-nv7e2n7d4vj5kfkc55mlmiiju0ch2qhn.apps.googleusercontent.com',
|
|
||||||
iosBundleId: 'com.example.prosappco',
|
|
||||||
);
|
|
||||||
|
|
||||||
static const FirebaseOptions macos = FirebaseOptions(
|
|
||||||
apiKey: 'AIzaSyDUhnkNkeEPkqcwVo5EWr63q3oEX6bDGic',
|
|
||||||
appId: '1:245204384533:ios:1d58f5624d0df4859f871c',
|
|
||||||
messagingSenderId: '245204384533',
|
|
||||||
projectId: 'prosapp-5747a',
|
|
||||||
databaseURL: 'https://prosapp-5747a-default-rtdb.firebaseio.com',
|
|
||||||
storageBucket: 'prosapp-5747a.appspot.com',
|
|
||||||
androidClientId: '245204384533-04oabs744hp8fjbdreijp4h16nvfmeti.apps.googleusercontent.com',
|
|
||||||
iosClientId: '245204384533-nv7e2n7d4vj5kfkc55mlmiiju0ch2qhn.apps.googleusercontent.com',
|
|
||||||
iosBundleId: 'com.example.prosappco',
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|||||||
+4
-51
@@ -8,7 +8,6 @@ import 'package:prosappco/src/providers/user_provider.dart';
|
|||||||
import 'package:prosappco/src/presentation/screens/calendar.dart';
|
import 'package:prosappco/src/presentation/screens/calendar.dart';
|
||||||
import 'package:prosappco/src/presentation/screens/city.dart';
|
import 'package:prosappco/src/presentation/screens/city.dart';
|
||||||
import 'package:prosappco/src/presentation/screens/login/login.dart';
|
import 'package:prosappco/src/presentation/screens/login/login.dart';
|
||||||
import 'package:firebase_core/firebase_core.dart';
|
|
||||||
import 'package:prosappco/src/presentation/screens/login/login_email.dart';
|
import 'package:prosappco/src/presentation/screens/login/login_email.dart';
|
||||||
import 'package:prosappco/src/presentation/screens/my_services.dart';
|
import 'package:prosappco/src/presentation/screens/my_services.dart';
|
||||||
import 'package:prosappco/src/presentation/screens/my_services_pro.dart';
|
import 'package:prosappco/src/presentation/screens/my_services_pro.dart';
|
||||||
@@ -24,46 +23,14 @@ import 'package:prosappco/src/presentation/screens/request_sent.dart';
|
|||||||
import 'package:prosappco/src/presentation/screens/reset_password/reset_password.dart';
|
import 'package:prosappco/src/presentation/screens/reset_password/reset_password.dart';
|
||||||
import 'package:prosappco/src/presentation/screens/map/service.dart';
|
import 'package:prosappco/src/presentation/screens/map/service.dart';
|
||||||
import 'package:prosappco/src/presentation/screens/solicitudes.dart';
|
import 'package:prosappco/src/presentation/screens/solicitudes.dart';
|
||||||
import 'package:prosappco/src/services/local_notifications.dart';
|
|
||||||
import 'firebase_options.dart';
|
|
||||||
import 'package:firebase_messaging/firebase_messaging.dart';
|
|
||||||
import 'package:flutter/foundation.dart' show kIsWeb;
|
import 'package:flutter/foundation.dart' show kIsWeb;
|
||||||
import 'package:provider/provider.dart';
|
import 'package:provider/provider.dart';
|
||||||
import 'package:flutter_localizations/flutter_localizations.dart';
|
import 'package:flutter_localizations/flutter_localizations.dart';
|
||||||
|
|
||||||
Future<void> _firebaseMessagingBackgroundHandler(RemoteMessage message) async {
|
|
||||||
await Firebase.initializeApp();
|
|
||||||
|
|
||||||
print('Handling a backdround message: ${message.messageId}');
|
|
||||||
}
|
|
||||||
|
|
||||||
void main() async {
|
void main() async {
|
||||||
WidgetsFlutterBinding.ensureInitialized();
|
WidgetsFlutterBinding.ensureInitialized();
|
||||||
await Firebase.initializeApp(
|
|
||||||
options: DefaultFirebaseOptions.currentPlatform,
|
|
||||||
).then((value) => Get.put(AuthenticationRepository()));
|
|
||||||
|
|
||||||
// Evitar la solicitud de permiso en la versión web
|
Get.put(AuthenticationRepository());
|
||||||
if (!kIsWeb) {
|
|
||||||
FirebaseMessaging messaging = FirebaseMessaging.instance;
|
|
||||||
|
|
||||||
await messaging.requestPermission(
|
|
||||||
alert: true,
|
|
||||||
announcement: false,
|
|
||||||
badge: true,
|
|
||||||
carPlay: false,
|
|
||||||
criticalAlert: false,
|
|
||||||
provisional: false,
|
|
||||||
sound: true,
|
|
||||||
);
|
|
||||||
|
|
||||||
FirebaseMessaging.onBackgroundMessage(
|
|
||||||
(_firebaseMessagingBackgroundHandler));
|
|
||||||
|
|
||||||
FirebaseMessaging.onMessage.listen((RemoteMessage message) {});
|
|
||||||
}
|
|
||||||
|
|
||||||
await initNotifications();
|
|
||||||
|
|
||||||
await initializeDateFormatting('es_MX', null);
|
await initializeDateFormatting('es_MX', null);
|
||||||
|
|
||||||
@@ -82,9 +49,7 @@ class MyApp extends StatelessWidget {
|
|||||||
Widget build(BuildContext context) {
|
Widget build(BuildContext context) {
|
||||||
return MultiProvider(
|
return MultiProvider(
|
||||||
providers: [
|
providers: [
|
||||||
ChangeNotifierProvider(
|
ChangeNotifierProvider(create: (_) => UserProvider()),
|
||||||
create: (_) => UserProvider(),
|
|
||||||
),
|
|
||||||
],
|
],
|
||||||
child: GetMaterialApp(
|
child: GetMaterialApp(
|
||||||
theme: ThemeData(fontFamily: 'Poppins'),
|
theme: ThemeData(fontFamily: 'Poppins'),
|
||||||
@@ -94,9 +59,7 @@ class MyApp extends StatelessWidget {
|
|||||||
GlobalWidgetsLocalizations.delegate,
|
GlobalWidgetsLocalizations.delegate,
|
||||||
GlobalCupertinoLocalizations.delegate,
|
GlobalCupertinoLocalizations.delegate,
|
||||||
],
|
],
|
||||||
supportedLocales: const [
|
supportedLocales: const [Locale('es', 'US')],
|
||||||
Locale('es', 'US'),
|
|
||||||
],
|
|
||||||
title: 'ProsApp',
|
title: 'ProsApp',
|
||||||
initialRoute: '/',
|
initialRoute: '/',
|
||||||
routes: {
|
routes: {
|
||||||
@@ -108,8 +71,7 @@ class MyApp extends StatelessWidget {
|
|||||||
'/city': (context) => const CityScreen(),
|
'/city': (context) => const CityScreen(),
|
||||||
'/profession': (context) => const ProfessionScreen(),
|
'/profession': (context) => const ProfessionScreen(),
|
||||||
'/newNumber': (context) => const NewNumberScreen(),
|
'/newNumber': (context) => const NewNumberScreen(),
|
||||||
'/profesionalRevision': (context) =>
|
'/profesionalRevision': (context) => const ProfessionalRevisionScreen(),
|
||||||
const ProfessionalRevisionScreen(),
|
|
||||||
'/profesionalProfile': (context) => const ProfessionalProfileScreen(),
|
'/profesionalProfile': (context) => const ProfessionalProfileScreen(),
|
||||||
'/solicitudEnviada': (context) => const RequestSentScreen(),
|
'/solicitudEnviada': (context) => const RequestSentScreen(),
|
||||||
'/nuevaPassword': (context) => NewPasswordScreen(),
|
'/nuevaPassword': (context) => NewPasswordScreen(),
|
||||||
@@ -142,7 +104,6 @@ class _SplashScreenState extends State<SplashScreen> {
|
|||||||
void initState() {
|
void initState() {
|
||||||
super.initState();
|
super.initState();
|
||||||
if (kIsWeb) {
|
if (kIsWeb) {
|
||||||
// Espera 3 segundos y luego redirige a LoginScreen
|
|
||||||
Future.delayed(const Duration(seconds: 3), () {
|
Future.delayed(const Duration(seconds: 3), () {
|
||||||
Navigator.pushReplacement(
|
Navigator.pushReplacement(
|
||||||
context,
|
context,
|
||||||
@@ -162,11 +123,3 @@ class _SplashScreenState extends State<SplashScreen> {
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
// splash: 'images/splashgif.gif',
|
|
||||||
// backgroundColor: Colors.black,
|
|
||||||
// nextScreen: const LoginScreen(),
|
|
||||||
// splashIconSize: 50,
|
|
||||||
// duration: 80000,
|
|
||||||
@@ -1,342 +1,82 @@
|
|||||||
import 'package:cloud_firestore/cloud_firestore.dart';
|
|
||||||
import 'package:firebase_auth/firebase_auth.dart';
|
|
||||||
import 'package:flutter/foundation.dart';
|
import 'package:flutter/foundation.dart';
|
||||||
import 'package:get/get.dart';
|
import 'package:get/get.dart';
|
||||||
import 'package:google_sign_in/google_sign_in.dart';
|
import 'package:prosappco/src/models/user_model.dart';
|
||||||
import 'package:prosappco/src/authentication/exceptions/register_failed.dart';
|
|
||||||
import 'package:prosappco/src/presentation/screens/login/login.dart';
|
import 'package:prosappco/src/presentation/screens/login/login.dart';
|
||||||
// import 'package:prosappco/src/presentation/screens/service.dart';
|
|
||||||
import 'package:prosappco/src/presentation/screens/map/service.dart';
|
import 'package:prosappco/src/presentation/screens/map/service.dart';
|
||||||
import 'package:prosappco/src/presentation/screens/service_web.dart';
|
import 'package:prosappco/src/presentation/screens/service_web.dart';
|
||||||
|
import 'package:prosappco/src/services/api_service.dart';
|
||||||
|
|
||||||
class AuthenticationRepository extends GetxController {
|
class AuthenticationRepository extends GetxController {
|
||||||
static AuthenticationRepository get instance => Get.find();
|
static AuthenticationRepository get instance => Get.find();
|
||||||
|
|
||||||
//Variables
|
final _api = ApiService.instance;
|
||||||
final _auth = FirebaseAuth.instance;
|
|
||||||
late final Rx<User?> firebaseUser;
|
final Rx<UserModel?> currentUser = Rx<UserModel?>(null);
|
||||||
final firebase = FirebaseFirestore.instance;
|
var isLoggedIn = false.obs;
|
||||||
final GoogleSignIn googleSignIn = GoogleSignIn();
|
|
||||||
// final _userRef = firebase
|
|
||||||
var verificationId = ''.obs;
|
|
||||||
|
|
||||||
@override
|
@override
|
||||||
void onReady() {
|
void onReady() {
|
||||||
// Future.delayed(const Duration(seconds: 6));
|
_checkSession();
|
||||||
firebaseUser = Rx<User?>(_auth.currentUser);
|
|
||||||
firebaseUser.bindStream(_auth.userChanges());
|
|
||||||
ever(firebaseUser, _setInitialScreen);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
_setInitialScreen(User? user) {
|
Future<void> _checkSession() async {
|
||||||
user == null
|
final token = await _api.getToken();
|
||||||
? Get.offAll(const LoginScreen())
|
if (token == null) {
|
||||||
: kIsWeb
|
Get.offAll(const LoginScreen());
|
||||||
? Get.offAll(const ServiceWebScreen())
|
return;
|
||||||
: Get.offAll(const ServiceScreen());
|
|
||||||
}
|
|
||||||
|
|
||||||
Future<void> phoneAuthentication(String phoneNo) async {
|
|
||||||
await _auth.verifyPhoneNumber(
|
|
||||||
phoneNumber: phoneNo,
|
|
||||||
verificationCompleted: (credential) async {
|
|
||||||
await _auth.signInWithCredential(credential);
|
|
||||||
},
|
|
||||||
codeSent: (verificationId, resendToken) {
|
|
||||||
this.verificationId.value = verificationId;
|
|
||||||
},
|
|
||||||
codeAutoRetrievalTimeout: (verificationId) {
|
|
||||||
this.verificationId.value = verificationId;
|
|
||||||
},
|
|
||||||
verificationFailed: (e) {
|
|
||||||
if (e.code == 'invalid-phone-number') {
|
|
||||||
Get.snackbar('Error', 'El numero no es valido.');
|
|
||||||
} else {
|
|
||||||
Get.snackbar('Error', 'Algo ha ido mal. Inténtalo de nuevo. $e');
|
|
||||||
}
|
|
||||||
},
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
Future<void> updatePhoneNumber(String verificationId, String smsCode) async {
|
|
||||||
try {
|
|
||||||
PhoneAuthCredential credential = PhoneAuthProvider.credential(
|
|
||||||
verificationId: verificationId, smsCode: smsCode);
|
|
||||||
await FirebaseAuth.instance.currentUser!.updatePhoneNumber(credential);
|
|
||||||
print("Phone number updated successfully");
|
|
||||||
} catch (e) {
|
|
||||||
print("Error updating phone number: $e");
|
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
Future<bool> verifyOTP(String otp) async {
|
|
||||||
var credentials = await _auth.signInWithCredential(
|
|
||||||
PhoneAuthProvider.credential(
|
|
||||||
verificationId: verificationId.value, smsCode: otp));
|
|
||||||
return credentials.user != null ? true : false;
|
|
||||||
}
|
|
||||||
|
|
||||||
Future<void> createUserWithEmailAndPassword(
|
|
||||||
String email, String password) async {
|
|
||||||
try {
|
try {
|
||||||
await _auth.createUserWithEmailAndPassword(
|
final data = await _api.get('/auth/me');
|
||||||
email: email, password: password);
|
currentUser.value = UserModel.fromApi(data);
|
||||||
|
isLoggedIn.value = true;
|
||||||
firebaseUser.value != null
|
_navigateHome();
|
||||||
? kIsWeb
|
|
||||||
? Get.offAll(const ServiceWebScreen())
|
|
||||||
: Get.to(const ServiceScreen())
|
|
||||||
: Get.to(const LoginScreen());
|
|
||||||
} on FirebaseAuthException catch (e) {
|
|
||||||
final ex = SignUpWithEmailAndPasswordFailure.code(e.code);
|
|
||||||
Get.snackbar(
|
|
||||||
'Correo ya registrado',
|
|
||||||
'Por favor pruebe con otro.',
|
|
||||||
snackPosition: SnackPosition.BOTTOM,
|
|
||||||
);
|
|
||||||
} catch (_) {
|
} catch (_) {
|
||||||
const ex = SignUpWithEmailAndPasswordFailure();
|
await _api.clearToken();
|
||||||
print('EXCEPTION - ${ex.message}');
|
Get.offAll(const LoginScreen());
|
||||||
throw ex;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void _navigateHome() {
|
||||||
|
kIsWeb
|
||||||
|
? Get.offAll(const ServiceWebScreen())
|
||||||
|
: Get.offAll(const ServiceScreen());
|
||||||
|
}
|
||||||
|
|
||||||
Future<void> loginWithEmailAndPassword(String email, String password) async {
|
Future<void> loginWithEmailAndPassword(String email, String password) async {
|
||||||
try {
|
final data = await _api.post('/auth/login', {'email': email, 'password': password});
|
||||||
await _auth.signInWithEmailAndPassword(email: email, password: password);
|
await _api.saveToken(data['access_token']);
|
||||||
} on FirebaseAuthException catch (e) {
|
currentUser.value = UserModel.fromApi(data['user']);
|
||||||
if (e.code == 'wrong-password') {
|
isLoggedIn.value = true;
|
||||||
Get.snackbar(
|
_navigateHome();
|
||||||
'Contraseña incorrecta',
|
|
||||||
'Por favor intentelo de nuevo.',
|
|
||||||
snackPosition: SnackPosition.BOTTOM,
|
|
||||||
);
|
|
||||||
}
|
|
||||||
if (e.code == 'invalid-email') {
|
|
||||||
Get.snackbar(
|
|
||||||
'Ingrese un email valido',
|
|
||||||
'Por favor pruebe con otro.',
|
|
||||||
snackPosition: SnackPosition.BOTTOM,
|
|
||||||
);
|
|
||||||
}
|
|
||||||
if (e.code == 'user-not-found') {
|
|
||||||
Get.snackbar(
|
|
||||||
'Email no encontrado',
|
|
||||||
'Este correo no se encuentra registrado.',
|
|
||||||
snackPosition: SnackPosition.BOTTOM,
|
|
||||||
);
|
|
||||||
}
|
|
||||||
} catch (_) {}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
Future<void> signInWithGoogle() async {
|
Future<void> createUserWithEmailAndPassword(String email, String password, String name) async {
|
||||||
try {
|
final data = await _api.post('/auth/register', {
|
||||||
final GoogleSignInAccount? googleUser = await GoogleSignIn().signIn();
|
'email': email,
|
||||||
|
'password': password,
|
||||||
if (googleUser != null) {
|
'name': name,
|
||||||
final GoogleSignInAuthentication googleAuth =
|
});
|
||||||
await googleUser.authentication;
|
await _api.saveToken(data['access_token']);
|
||||||
final OAuthCredential credential = GoogleAuthProvider.credential(
|
currentUser.value = UserModel.fromApi(data['user']);
|
||||||
accessToken: googleAuth.accessToken,
|
isLoggedIn.value = true;
|
||||||
idToken: googleAuth.idToken,
|
_navigateHome();
|
||||||
);
|
|
||||||
|
|
||||||
await FirebaseAuth.instance.signInWithCredential(credential);
|
|
||||||
|
|
||||||
// Continúa con el flujo de la aplicación después del inicio de sesión exitoso
|
|
||||||
// Por ejemplo, redirecciona a la siguiente pantalla
|
|
||||||
firebaseUser.value != null
|
|
||||||
? kIsWeb
|
|
||||||
? Get.offAll(const ServiceWebScreen())
|
|
||||||
: Get.to(const ServiceScreen())
|
|
||||||
: Get.to(const LoginScreen());
|
|
||||||
} else {
|
|
||||||
// El usuario canceló el inicio de sesión con Google
|
|
||||||
// Puedes manejarlo según tus necesidades
|
|
||||||
print('Error');
|
|
||||||
}
|
|
||||||
} catch (e) {
|
|
||||||
print('Error - $e');
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
Future<void> logout(String uid) async {
|
Future<void> phoneAuthentication(String phone, {String? name}) async {
|
||||||
try {
|
final data = await _api.post('/auth/phone', {'phone': phone, 'name': name ?? phone});
|
||||||
await FirebaseFirestore.instance
|
await _api.saveToken(data['access_token']);
|
||||||
.collection('users')
|
currentUser.value = UserModel.fromApi(data['user']);
|
||||||
.doc(uid)
|
isLoggedIn.value = true;
|
||||||
.update({'token': FieldValue.delete()});
|
_navigateHome();
|
||||||
} catch (e) {
|
|
||||||
print(e);
|
|
||||||
}
|
|
||||||
|
|
||||||
_auth.signOut();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
String? getCurrentUserPhone() {
|
Future<void> logout() async {
|
||||||
final User? user = _auth.currentUser;
|
await _api.clearToken();
|
||||||
return user?.phoneNumber;
|
currentUser.value = null;
|
||||||
|
isLoggedIn.value = false;
|
||||||
|
Get.offAll(const LoginScreen());
|
||||||
}
|
}
|
||||||
|
|
||||||
String? getCurrentUserUid() {
|
String? getCurrentUserUid() => currentUser.value?.id;
|
||||||
final User? user = _auth.currentUser;
|
String? getCurrentUserPhone() => currentUser.value?.phoneNumber;
|
||||||
return user?.uid;
|
|
||||||
}
|
|
||||||
|
|
||||||
Future<String> getCity(String uid) async {
|
|
||||||
String city = '';
|
|
||||||
try {
|
|
||||||
final snapshot =
|
|
||||||
await FirebaseFirestore.instance.collection('users').doc(uid).get();
|
|
||||||
final Map<String, dynamic>? data = snapshot.data();
|
|
||||||
city = data?['city'] ?? '';
|
|
||||||
} catch (e) {
|
|
||||||
print('Error getting city: $e');
|
|
||||||
}
|
|
||||||
return city;
|
|
||||||
}
|
|
||||||
|
|
||||||
Future<String> getGender(String uid) async {
|
|
||||||
String gender = '';
|
|
||||||
try {
|
|
||||||
final snapshot =
|
|
||||||
await FirebaseFirestore.instance.collection('users').doc(uid).get();
|
|
||||||
final Map<String, dynamic>? data = snapshot.data();
|
|
||||||
gender = data?['gender'] ?? '';
|
|
||||||
} catch (e) {
|
|
||||||
print('Error getting gender: $e');
|
|
||||||
}
|
|
||||||
return gender;
|
|
||||||
}
|
|
||||||
|
|
||||||
Future<String> getBirthday(String uid) async {
|
|
||||||
String birthDate = '';
|
|
||||||
try {
|
|
||||||
final snapshot =
|
|
||||||
await FirebaseFirestore.instance.collection('users').doc(uid).get();
|
|
||||||
final Map<String, dynamic>? data = snapshot.data();
|
|
||||||
birthDate = data?['birth_date'] ?? '';
|
|
||||||
} catch (e) {
|
|
||||||
print('Error getting birthDate: $e');
|
|
||||||
}
|
|
||||||
return birthDate;
|
|
||||||
}
|
|
||||||
|
|
||||||
Future<String> getCoordsOfCity(String uid) async {
|
|
||||||
String coords = '';
|
|
||||||
try {
|
|
||||||
final snapshot =
|
|
||||||
await FirebaseFirestore.instance.collection('users').doc(uid).get();
|
|
||||||
final Map<String, dynamic>? data = snapshot.data();
|
|
||||||
coords = data?['coordsOfCity'] ?? '';
|
|
||||||
} catch (e) {
|
|
||||||
print('Error getting coords of city: $e');
|
|
||||||
}
|
|
||||||
print('Error getting coords of city: $coords');
|
|
||||||
return coords;
|
|
||||||
}
|
|
||||||
|
|
||||||
Future<String> getAddress(String uid) async {
|
|
||||||
String address = '';
|
|
||||||
try {
|
|
||||||
final snapshot =
|
|
||||||
await FirebaseFirestore.instance.collection('users').doc(uid).get();
|
|
||||||
final Map<String, dynamic>? data = snapshot.data();
|
|
||||||
address = data?['address'] ?? '';
|
|
||||||
} catch (e) {
|
|
||||||
print('Error getting address: $e');
|
|
||||||
}
|
|
||||||
return address;
|
|
||||||
}
|
|
||||||
|
|
||||||
Future<String> getUbicacion(String uid) async {
|
|
||||||
String location = '';
|
|
||||||
try {
|
|
||||||
final snapshot =
|
|
||||||
await FirebaseFirestore.instance.collection('users').doc(uid).get();
|
|
||||||
final Map<String, dynamic>? data = snapshot.data();
|
|
||||||
location = data?['ubicacion'] ?? '';
|
|
||||||
} catch (e) {
|
|
||||||
print('Error getting address: $e');
|
|
||||||
}
|
|
||||||
return location;
|
|
||||||
}
|
|
||||||
|
|
||||||
Future<String> getOpcionalAddress(String uid) async {
|
|
||||||
String opcional_location = '';
|
|
||||||
try {
|
|
||||||
final snapshot =
|
|
||||||
await FirebaseFirestore.instance.collection('users').doc(uid).get();
|
|
||||||
final Map<String, dynamic>? data = snapshot.data();
|
|
||||||
opcional_location = data?['opcional_address'] ?? '';
|
|
||||||
} catch (e) {
|
|
||||||
print('Error getting address: $e');
|
|
||||||
}
|
|
||||||
return opcional_location;
|
|
||||||
}
|
|
||||||
|
|
||||||
Future<int> getTarifa(String uid) async {
|
|
||||||
int tarifa = 0;
|
|
||||||
try {
|
|
||||||
final snapshot =
|
|
||||||
await FirebaseFirestore.instance.collection('users').doc(uid).get();
|
|
||||||
final Map<String, dynamic>? data = snapshot.data();
|
|
||||||
tarifa = data?['tarifas'] ?? 0;
|
|
||||||
} catch (e) {
|
|
||||||
print('Error getting tarifa: $e');
|
|
||||||
}
|
|
||||||
return tarifa;
|
|
||||||
}
|
|
||||||
|
|
||||||
Future<String> getPhoto(String uid) async {
|
|
||||||
String photo = '';
|
|
||||||
try {
|
|
||||||
final snapshot =
|
|
||||||
await FirebaseFirestore.instance.collection('users').doc(uid).get();
|
|
||||||
final Map<String, dynamic>? data = snapshot.data();
|
|
||||||
photo = data?['photo'] ?? '';
|
|
||||||
} catch (e) {
|
|
||||||
print('Error getting photo: $e');
|
|
||||||
}
|
|
||||||
return photo;
|
|
||||||
}
|
|
||||||
|
|
||||||
Future<String> getBanner(String uid) async {
|
|
||||||
String photo = '';
|
|
||||||
try {
|
|
||||||
final snapshot =
|
|
||||||
await FirebaseFirestore.instance.collection('users').doc(uid).get();
|
|
||||||
final Map<String, dynamic>? data = snapshot.data();
|
|
||||||
photo = data?['banner'] ?? '';
|
|
||||||
} catch (e) {
|
|
||||||
print('Error getting banner: $e');
|
|
||||||
}
|
|
||||||
return photo;
|
|
||||||
}
|
|
||||||
|
|
||||||
Future<String> getProfession(String uid) async {
|
|
||||||
String profession = '';
|
|
||||||
try {
|
|
||||||
final snapshot =
|
|
||||||
await FirebaseFirestore.instance.collection('users').doc(uid).get();
|
|
||||||
final Map<String, dynamic>? data = snapshot.data();
|
|
||||||
profession = data?['profesion'] ?? '';
|
|
||||||
} catch (e) {
|
|
||||||
print('Error getting profesion: $e');
|
|
||||||
}
|
|
||||||
return profession;
|
|
||||||
}
|
|
||||||
|
|
||||||
Future<String> getState(String uid) async {
|
|
||||||
String state = '';
|
|
||||||
try {
|
|
||||||
final snapshot =
|
|
||||||
await FirebaseFirestore.instance.collection('users').doc(uid).get();
|
|
||||||
final Map<String, dynamic>? data = snapshot.data();
|
|
||||||
state = data?['estado'] ?? '';
|
|
||||||
} catch (e) {
|
|
||||||
print('Error getting estado: $e');
|
|
||||||
}
|
|
||||||
return state;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,66 +1,45 @@
|
|||||||
import 'dart:io';
|
import 'dart:io';
|
||||||
|
|
||||||
import 'package:firebase_storage/firebase_storage.dart';
|
|
||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
import 'package:flutter_animate/flutter_animate.dart';
|
|
||||||
import 'package:prosappco/src/components/column_padding.dart';
|
import 'package:prosappco/src/components/column_padding.dart';
|
||||||
|
|
||||||
const double photoSize = 150;
|
const double photoSize = 150;
|
||||||
|
|
||||||
class ReferenceBannerPhoto extends StatelessWidget {
|
class ReferenceBannerPhoto extends StatelessWidget {
|
||||||
Reference? ref;
|
/// Accepts either a String URL or null.
|
||||||
double size;
|
final Object? ref;
|
||||||
double sizeCircle;
|
final double size;
|
||||||
|
final double sizeCircle;
|
||||||
|
|
||||||
ReferenceBannerPhoto({
|
const ReferenceBannerPhoto({
|
||||||
super.key,
|
super.key,
|
||||||
required this.ref,
|
required this.ref,
|
||||||
this.size = photoSize,
|
this.size = photoSize,
|
||||||
this.sizeCircle = photoSize,
|
this.sizeCircle = photoSize,
|
||||||
});
|
});
|
||||||
|
|
||||||
Future<Widget> downloadImage() async {
|
|
||||||
try {
|
|
||||||
if (ref != null) {
|
|
||||||
final imageData = await ref!.getData();
|
|
||||||
if (imageData != null) {
|
|
||||||
return Image.memory(
|
|
||||||
imageData,
|
|
||||||
width: double.infinity,
|
|
||||||
height: size,
|
|
||||||
fit: BoxFit.fill,
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
// ignore: empty_catches
|
|
||||||
} catch (e) {}
|
|
||||||
|
|
||||||
return DefaultPhoto(
|
|
||||||
sizeDefault: sizeCircle,
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
@override
|
@override
|
||||||
Widget build(BuildContext context) {
|
Widget build(BuildContext context) {
|
||||||
return FutureBuilder<Widget>(
|
final url = ref is String ? ref as String : null;
|
||||||
future: downloadImage(),
|
|
||||||
builder: (BuildContext context, AsyncSnapshot<Widget> snapshot) {
|
if (url == null || url.isEmpty || url.startsWith('...')) {
|
||||||
if (snapshot.connectionState == ConnectionState.waiting) {
|
return DefaultPhoto(sizeDefault: sizeCircle);
|
||||||
// mientras la llamada asíncrona está en proceso, muestra un mensaje de carga
|
}
|
||||||
return DefaultPhoto();
|
|
||||||
} else if (snapshot.connectionState == ConnectionState.done &&
|
return Image.network(
|
||||||
snapshot.hasData) {
|
url,
|
||||||
return snapshot.data!;
|
width: double.infinity,
|
||||||
} else {
|
height: size,
|
||||||
return DefaultPhoto();
|
fit: BoxFit.fill,
|
||||||
}
|
errorBuilder: (_, __, ___) => DefaultPhoto(sizeDefault: sizeCircle),
|
||||||
});
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
class DefaultPhoto extends StatelessWidget {
|
class DefaultPhoto extends StatelessWidget {
|
||||||
double sizeDefault;
|
final double sizeDefault;
|
||||||
DefaultPhoto({
|
|
||||||
|
const DefaultPhoto({
|
||||||
super.key,
|
super.key,
|
||||||
this.sizeDefault = photoSize,
|
this.sizeDefault = photoSize,
|
||||||
});
|
});
|
||||||
@@ -92,14 +71,14 @@ class DefaultPhoto extends StatelessWidget {
|
|||||||
color: Colors.grey.withOpacity(0.3),
|
color: Colors.grey.withOpacity(0.3),
|
||||||
spreadRadius: 2,
|
spreadRadius: 2,
|
||||||
blurRadius: 5,
|
blurRadius: 5,
|
||||||
offset: const Offset(0, 3), // changes position of shadow
|
offset: const Offset(0, 3),
|
||||||
),
|
),
|
||||||
],
|
],
|
||||||
),
|
),
|
||||||
constraints: const BoxConstraints(minWidth: 250, minHeight: 50),
|
constraints: const BoxConstraints(minWidth: 250, minHeight: 50),
|
||||||
child: Row(
|
child: const Row(
|
||||||
mainAxisAlignment: MainAxisAlignment.center,
|
mainAxisAlignment: MainAxisAlignment.center,
|
||||||
children: const [
|
children: [
|
||||||
Expanded(
|
Expanded(
|
||||||
child: Center(
|
child: Center(
|
||||||
child: Text(
|
child: Text(
|
||||||
@@ -129,8 +108,9 @@ class DefaultPhoto extends StatelessWidget {
|
|||||||
}
|
}
|
||||||
|
|
||||||
class LocalPhoto extends StatelessWidget {
|
class LocalPhoto extends StatelessWidget {
|
||||||
File file;
|
final File file;
|
||||||
LocalPhoto({super.key, required this.file});
|
|
||||||
|
const LocalPhoto({super.key, required this.file});
|
||||||
|
|
||||||
@override
|
@override
|
||||||
Widget build(BuildContext context) {
|
Widget build(BuildContext context) {
|
||||||
|
|||||||
@@ -1,4 +1,3 @@
|
|||||||
import 'package:firebase_auth/firebase_auth.dart';
|
|
||||||
import 'package:flutter/cupertino.dart';
|
import 'package:flutter/cupertino.dart';
|
||||||
import 'package:flutter/foundation.dart';
|
import 'package:flutter/foundation.dart';
|
||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
@@ -25,7 +24,6 @@ class DrawerProfessional extends StatefulWidget {
|
|||||||
|
|
||||||
class _DrawerProfessionalState extends State<DrawerProfessional> {
|
class _DrawerProfessionalState extends State<DrawerProfessional> {
|
||||||
final uid = AuthenticationRepository.instance.getCurrentUserUid();
|
final uid = AuthenticationRepository.instance.getCurrentUserUid();
|
||||||
UserModel? user;
|
|
||||||
ScoresModel? scoresModel;
|
ScoresModel? scoresModel;
|
||||||
|
|
||||||
Future<void> _irSugerencias() async {
|
Future<void> _irSugerencias() async {
|
||||||
@@ -41,12 +39,6 @@ class _DrawerProfessionalState extends State<DrawerProfessional> {
|
|||||||
void initState() {
|
void initState() {
|
||||||
super.initState();
|
super.initState();
|
||||||
|
|
||||||
if (user == null) {
|
|
||||||
UserModel.getUser(uid.toString()).then(
|
|
||||||
(UserModel s) => setState(() => user = s),
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (scoresModel == null) {
|
if (scoresModel == null) {
|
||||||
ScoresModel.scoreTo(uid.toString(), true, false).then(
|
ScoresModel.scoreTo(uid.toString(), true, false).then(
|
||||||
(ScoresModel s) => setState(() => scoresModel = s),
|
(ScoresModel s) => setState(() => scoresModel = s),
|
||||||
@@ -56,7 +48,9 @@ class _DrawerProfessionalState extends State<DrawerProfessional> {
|
|||||||
|
|
||||||
@override
|
@override
|
||||||
Widget build(BuildContext context) {
|
Widget build(BuildContext context) {
|
||||||
final User? currentUser = FirebaseAuth.instance.currentUser;
|
// Use in-memory currentUser — no Firebase needed
|
||||||
|
final UserModel? currentUser =
|
||||||
|
AuthenticationRepository.instance.currentUser.value;
|
||||||
|
|
||||||
return Drawer(
|
return Drawer(
|
||||||
child: Container(
|
child: Container(
|
||||||
@@ -78,7 +72,7 @@ class _DrawerProfessionalState extends State<DrawerProfessional> {
|
|||||||
),
|
),
|
||||||
);
|
);
|
||||||
},
|
},
|
||||||
title: Text(user?.name ?? '',
|
title: Text(currentUser?.name ?? '',
|
||||||
style: const TextStyle(fontWeight: FontWeight.bold)),
|
style: const TextStyle(fontWeight: FontWeight.bold)),
|
||||||
subtitle: Column(
|
subtitle: Column(
|
||||||
crossAxisAlignment: CrossAxisAlignment.start,
|
crossAxisAlignment: CrossAxisAlignment.start,
|
||||||
@@ -88,13 +82,13 @@ class _DrawerProfessionalState extends State<DrawerProfessional> {
|
|||||||
style: const TextStyle(fontSize: 12),
|
style: const TextStyle(fontSize: 12),
|
||||||
),
|
),
|
||||||
Text(
|
Text(
|
||||||
user?.city ?? '',
|
currentUser?.city ?? '',
|
||||||
style: const TextStyle(fontSize: 12),
|
style: const TextStyle(fontSize: 12),
|
||||||
),
|
),
|
||||||
],
|
],
|
||||||
),
|
),
|
||||||
leading: ReferencePhoto(
|
leading: ReferencePhoto(
|
||||||
ref: user?.photo,
|
ref: currentUser?.picture,
|
||||||
size: 55,
|
size: 55,
|
||||||
sizeCircle: 60,
|
sizeCircle: 60,
|
||||||
),
|
),
|
||||||
@@ -113,7 +107,7 @@ class _DrawerProfessionalState extends State<DrawerProfessional> {
|
|||||||
color: Colors.grey.withOpacity(0.3),
|
color: Colors.grey.withOpacity(0.3),
|
||||||
spreadRadius: 1,
|
spreadRadius: 1,
|
||||||
blurRadius: 3,
|
blurRadius: 3,
|
||||||
offset: const Offset(0, 0), // changes position of shadow
|
offset: const Offset(0, 0),
|
||||||
),
|
),
|
||||||
],
|
],
|
||||||
),
|
),
|
||||||
@@ -234,25 +228,6 @@ class _DrawerProfessionalState extends State<DrawerProfessional> {
|
|||||||
style: TextStyle(fontSize: 15),
|
style: TextStyle(fontSize: 15),
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
// ListTile(
|
|
||||||
// onTap: () {
|
|
||||||
// Navigator.of(context).push(
|
|
||||||
// CupertinoPageRoute(
|
|
||||||
// builder: (BuildContext context) {
|
|
||||||
// return const MessagesScreen();
|
|
||||||
// },
|
|
||||||
// ),
|
|
||||||
// );
|
|
||||||
// },
|
|
||||||
// leading: const Icon(
|
|
||||||
// Icons.messenger_outline,
|
|
||||||
// color: Colors.black,
|
|
||||||
// ),
|
|
||||||
// title: const Text(
|
|
||||||
// 'Mensajes',
|
|
||||||
// style: TextStyle(fontSize: 15),
|
|
||||||
// ),
|
|
||||||
// ),
|
|
||||||
ListTile(
|
ListTile(
|
||||||
onTap: () {
|
onTap: () {
|
||||||
Navigator.push(
|
Navigator.push(
|
||||||
@@ -308,8 +283,7 @@ class _DrawerProfessionalState extends State<DrawerProfessional> {
|
|||||||
color: Colors.grey.withOpacity(0.5),
|
color: Colors.grey.withOpacity(0.5),
|
||||||
spreadRadius: 2,
|
spreadRadius: 2,
|
||||||
blurRadius: 3,
|
blurRadius: 3,
|
||||||
offset:
|
offset: const Offset(0, 2),
|
||||||
const Offset(0, 2), // changes position of shadow
|
|
||||||
),
|
),
|
||||||
],
|
],
|
||||||
),
|
),
|
||||||
@@ -407,7 +381,7 @@ class _DrawerProfessionalState extends State<DrawerProfessional> {
|
|||||||
const SizedBox(height: 5),
|
const SizedBox(height: 5),
|
||||||
ElevatedButton(
|
ElevatedButton(
|
||||||
onPressed: () {
|
onPressed: () {
|
||||||
AuthenticationRepository.instance.logout(uid!);
|
AuthenticationRepository.instance.logout();
|
||||||
},
|
},
|
||||||
style: ElevatedButton.styleFrom(
|
style: ElevatedButton.styleFrom(
|
||||||
backgroundColor: Colors.red,
|
backgroundColor: Colors.red,
|
||||||
|
|||||||
@@ -1,19 +1,19 @@
|
|||||||
import 'dart:io';
|
import 'dart:io';
|
||||||
|
|
||||||
import 'package:firebase_storage/firebase_storage.dart';
|
|
||||||
import 'package:flutter/foundation.dart';
|
import 'package:flutter/foundation.dart';
|
||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
import 'package:flutter_animate/flutter_animate.dart';
|
|
||||||
|
|
||||||
const double photoSize = 100;
|
const double photoSize = 100;
|
||||||
const double iconSize = 35;
|
const double iconSize = 35;
|
||||||
|
|
||||||
class ReferencePhoto extends StatelessWidget {
|
class ReferencePhoto extends StatelessWidget {
|
||||||
Reference? ref;
|
/// Accepts either a String URL or null.
|
||||||
double size;
|
final Object? ref;
|
||||||
double sizeIcon;
|
final double size;
|
||||||
double sizeCircle;
|
final double sizeIcon;
|
||||||
ReferencePhoto({
|
final double sizeCircle;
|
||||||
|
|
||||||
|
const ReferencePhoto({
|
||||||
super.key,
|
super.key,
|
||||||
required this.ref,
|
required this.ref,
|
||||||
this.sizeIcon = iconSize,
|
this.sizeIcon = iconSize,
|
||||||
@@ -21,58 +21,32 @@ class ReferencePhoto extends StatelessWidget {
|
|||||||
this.sizeCircle = photoSize,
|
this.sizeCircle = photoSize,
|
||||||
});
|
});
|
||||||
|
|
||||||
Future<Widget> downloadImage() async {
|
|
||||||
try {
|
|
||||||
if (ref != null) {
|
|
||||||
final imageData = await ref!.getData();
|
|
||||||
if (imageData != null) {
|
|
||||||
return ClipOval(
|
|
||||||
child: Image.memory(
|
|
||||||
imageData,
|
|
||||||
width: size,
|
|
||||||
height: size,
|
|
||||||
fit: BoxFit.cover,
|
|
||||||
),
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
// ignore: empty_catches
|
|
||||||
} catch (e) {}
|
|
||||||
|
|
||||||
return DefaultPhoto(
|
|
||||||
sizeDefault: sizeCircle,
|
|
||||||
iconDefault: sizeIcon,
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
@override
|
@override
|
||||||
Widget build(BuildContext context) {
|
Widget build(BuildContext context) {
|
||||||
return FutureBuilder<Widget>(
|
final url = ref is String ? ref as String : null;
|
||||||
future: downloadImage(),
|
|
||||||
builder: (BuildContext context, AsyncSnapshot<Widget> snapshot) {
|
if (url == null || url.isEmpty || url.startsWith('...')) {
|
||||||
if (snapshot.connectionState == ConnectionState.waiting) {
|
return DefaultPhoto(sizeDefault: sizeCircle, iconDefault: sizeIcon);
|
||||||
// mientras la llamada asíncrona está en proceso, muestra un mensaje de carga
|
}
|
||||||
return SizedBox(
|
|
||||||
width: size,
|
return ClipOval(
|
||||||
height: size,
|
child: Image.network(
|
||||||
child: Center(
|
url,
|
||||||
child: DefaultPhoto(),
|
width: size,
|
||||||
),
|
height: size,
|
||||||
);
|
fit: BoxFit.cover,
|
||||||
} else if (snapshot.connectionState == ConnectionState.done &&
|
errorBuilder: (_, __, ___) =>
|
||||||
snapshot.hasData) {
|
DefaultPhoto(sizeDefault: sizeCircle, iconDefault: sizeIcon),
|
||||||
return snapshot.data!;
|
),
|
||||||
} else {
|
);
|
||||||
return DefaultPhoto();
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
class DefaultPhoto extends StatelessWidget {
|
class DefaultPhoto extends StatelessWidget {
|
||||||
double sizeDefault;
|
final double sizeDefault;
|
||||||
double iconDefault;
|
final double iconDefault;
|
||||||
DefaultPhoto({
|
|
||||||
|
const DefaultPhoto({
|
||||||
super.key,
|
super.key,
|
||||||
this.sizeDefault = photoSize,
|
this.sizeDefault = photoSize,
|
||||||
this.iconDefault = iconSize,
|
this.iconDefault = iconSize,
|
||||||
@@ -97,8 +71,9 @@ class DefaultPhoto extends StatelessWidget {
|
|||||||
}
|
}
|
||||||
|
|
||||||
class LocalPhoto extends StatelessWidget {
|
class LocalPhoto extends StatelessWidget {
|
||||||
File file;
|
final File file;
|
||||||
LocalPhoto({super.key, required this.file});
|
|
||||||
|
const LocalPhoto({super.key, required this.file});
|
||||||
|
|
||||||
@override
|
@override
|
||||||
Widget build(BuildContext context) {
|
Widget build(BuildContext context) {
|
||||||
|
|||||||
@@ -1,18 +1,16 @@
|
|||||||
import 'package:firebase_storage/firebase_storage.dart';
|
|
||||||
import 'package:flutter/foundation.dart';
|
|
||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
import 'package:flutter_animate/flutter_animate.dart';
|
|
||||||
|
|
||||||
const double photoSize = 100;
|
const double photoSize = 100;
|
||||||
const double iconSize = 55;
|
const double iconSize = 55;
|
||||||
|
|
||||||
class ReferencePhotoWeb extends StatelessWidget {
|
class ReferencePhotoWeb extends StatelessWidget {
|
||||||
Reference? ref;
|
/// Accepts either a String URL or null.
|
||||||
double size;
|
final Object? ref;
|
||||||
double sizeIcon;
|
final double size;
|
||||||
double sizeCircle;
|
final double sizeIcon;
|
||||||
|
final double sizeCircle;
|
||||||
|
|
||||||
ReferencePhotoWeb({
|
const ReferencePhotoWeb({
|
||||||
super.key,
|
super.key,
|
||||||
required this.ref,
|
required this.ref,
|
||||||
this.sizeIcon = iconSize,
|
this.sizeIcon = iconSize,
|
||||||
@@ -20,59 +18,32 @@ class ReferencePhotoWeb extends StatelessWidget {
|
|||||||
this.sizeCircle = photoSize,
|
this.sizeCircle = photoSize,
|
||||||
});
|
});
|
||||||
|
|
||||||
Future<Widget> downloadImage() async {
|
|
||||||
try {
|
|
||||||
if (ref != null) {
|
|
||||||
final imageData = await ref!.getData();
|
|
||||||
if (imageData != null) {
|
|
||||||
return ClipOval(
|
|
||||||
child: Image.memory(
|
|
||||||
imageData,
|
|
||||||
width: size,
|
|
||||||
height: size,
|
|
||||||
fit: BoxFit.cover,
|
|
||||||
),
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
// ignore: empty_catches
|
|
||||||
} catch (e) {}
|
|
||||||
|
|
||||||
return DefaultPhotoWeb(
|
|
||||||
sizeDefault: sizeCircle,
|
|
||||||
iconDefault: sizeIcon,
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
@override
|
@override
|
||||||
Widget build(BuildContext context) {
|
Widget build(BuildContext context) {
|
||||||
return FutureBuilder<Widget>(
|
final url = ref is String ? ref as String : null;
|
||||||
future: downloadImage(),
|
|
||||||
builder: (BuildContext context, AsyncSnapshot<Widget> snapshot) {
|
if (url == null || url.isEmpty || url.startsWith('...')) {
|
||||||
if (snapshot.connectionState == ConnectionState.waiting) {
|
return DefaultPhotoWeb(sizeDefault: sizeCircle, iconDefault: sizeIcon);
|
||||||
// mientras la llamada asíncrona está en proceso, muestra un mensaje de carga
|
}
|
||||||
return SizedBox(
|
|
||||||
width: size,
|
return ClipOval(
|
||||||
height: size,
|
child: Image.network(
|
||||||
child: const Center(
|
url,
|
||||||
child: CircularProgressIndicator(),
|
width: size,
|
||||||
),
|
height: size,
|
||||||
);
|
fit: BoxFit.cover,
|
||||||
} else if (snapshot.connectionState == ConnectionState.done &&
|
errorBuilder: (_, __, ___) =>
|
||||||
snapshot.hasData) {
|
DefaultPhotoWeb(sizeDefault: sizeCircle, iconDefault: sizeIcon),
|
||||||
return snapshot.data!;
|
),
|
||||||
} else {
|
);
|
||||||
return DefaultPhotoWeb();
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
class DefaultPhotoWeb extends StatelessWidget {
|
class DefaultPhotoWeb extends StatelessWidget {
|
||||||
double sizeDefault;
|
final double sizeDefault;
|
||||||
double iconDefault;
|
final double iconDefault;
|
||||||
|
|
||||||
DefaultPhotoWeb({
|
const DefaultPhotoWeb({
|
||||||
super.key,
|
super.key,
|
||||||
this.sizeDefault = photoSize,
|
this.sizeDefault = photoSize,
|
||||||
this.iconDefault = iconSize,
|
this.iconDefault = iconSize,
|
||||||
@@ -97,8 +68,9 @@ class DefaultPhotoWeb extends StatelessWidget {
|
|||||||
}
|
}
|
||||||
|
|
||||||
class LocalPhotoWeb extends StatelessWidget {
|
class LocalPhotoWeb extends StatelessWidget {
|
||||||
Uint8List? file;
|
final Uint8List? file;
|
||||||
LocalPhotoWeb({super.key, required this.file});
|
|
||||||
|
const LocalPhotoWeb({super.key, required this.file});
|
||||||
|
|
||||||
@override
|
@override
|
||||||
Widget build(BuildContext context) {
|
Widget build(BuildContext context) {
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
import 'package:cloud_firestore/cloud_firestore.dart';
|
|
||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
import 'package:intl/intl.dart';
|
import 'package:intl/intl.dart';
|
||||||
|
import 'package:prosappco/src/services/api_service.dart';
|
||||||
|
|
||||||
typedef TimeCallback = void Function(TimeOfDay? pickedTime);
|
typedef TimeCallback = void Function(TimeOfDay? pickedTime);
|
||||||
|
|
||||||
@@ -185,28 +185,26 @@ class Schedule {
|
|||||||
'range2Hour1: $range2Hour1, range2Hour2: $range2Hour2)';
|
'range2Hour1: $range2Hour1, range2Hour2: $range2Hour2)';
|
||||||
}
|
}
|
||||||
|
|
||||||
|
static Map<String, Schedule> _emptySchedules() => {
|
||||||
|
for (var i = 1; i <= 7; i++)
|
||||||
|
'$i': Schedule(false, false, null, null, null, null),
|
||||||
|
};
|
||||||
|
|
||||||
static Future<Map<String, Schedule>> getHorarios(String uid) async {
|
static Future<Map<String, Schedule>> getHorarios(String uid) async {
|
||||||
try {
|
try {
|
||||||
final snapshot =
|
final Map<String, dynamic> data =
|
||||||
await FirebaseFirestore.instance.collection('users').doc(uid).get();
|
await ApiService.instance.get('/users/$uid');
|
||||||
final Map<String, dynamic>? data = snapshot.data();
|
final Map<String, dynamic>? horarioData =
|
||||||
final Map<String, dynamic>? horarioData = data?['horario'];
|
data['horario'] as Map<String, dynamic>?;
|
||||||
|
if (horarioData == null) return _emptySchedules();
|
||||||
final horarios = <String, Schedule>{};
|
final horarios = <String, Schedule>{};
|
||||||
horarioData?.forEach((key, value) {
|
horarioData.forEach((key, value) {
|
||||||
horarios[key] = Schedule.fromJson(value);
|
horarios[key] = Schedule.fromJson(value as Map<String, dynamic>);
|
||||||
});
|
});
|
||||||
return horarios;
|
return horarios;
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
print('Error getting user: $e');
|
print('Error getting horarios: $e');
|
||||||
return {
|
return _emptySchedules();
|
||||||
"1": Schedule(false, false, null, null, null, null),
|
|
||||||
"2": Schedule(false, false, null, null, null, null),
|
|
||||||
"3": Schedule(false, false, null, null, null, null),
|
|
||||||
"4": Schedule(false, false, null, null, null, null),
|
|
||||||
"5": Schedule(false, false, null, null, null, null),
|
|
||||||
"6": Schedule(false, false, null, null, null, null),
|
|
||||||
"7": Schedule(false, false, null, null, null, null),
|
|
||||||
};
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,116 +1,25 @@
|
|||||||
import 'package:firebase_auth/firebase_auth.dart';
|
|
||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
import 'package:get/get.dart';
|
import 'package:get/get.dart';
|
||||||
|
import 'package:prosappco/src/services/api_service.dart';
|
||||||
|
|
||||||
class NewPhoneController extends GetxController {
|
class NewPhoneController extends GetxController {
|
||||||
final FirebaseAuth _auth = FirebaseAuth.instance;
|
|
||||||
|
|
||||||
final newPhoneNo = TextEditingController(text: '');
|
final newPhoneNo = TextEditingController(text: '');
|
||||||
final otpCode = TextEditingController(text: '');
|
final otpCode = TextEditingController(text: '');
|
||||||
|
|
||||||
Future<void> updatePhoneNumber(newPhoneNo) async {
|
Future<void> updatePhoneNumber(String newPhone) async {
|
||||||
final currentUser = _auth.currentUser;
|
try {
|
||||||
|
await ApiService.instance.patch('/users/me', {'phone': newPhone});
|
||||||
if (currentUser?.phoneNumber == newPhoneNo) {
|
|
||||||
Get.snackbar(
|
Get.snackbar(
|
||||||
'Ya estas registrado',
|
'Número de teléfono actualizado',
|
||||||
'Este es tu numero actual.',
|
'El número de teléfono se ha actualizado correctamente.',
|
||||||
|
snackPosition: SnackPosition.BOTTOM,
|
||||||
|
);
|
||||||
|
} catch (e) {
|
||||||
|
Get.snackbar(
|
||||||
|
'Error al actualizar',
|
||||||
|
'No se pudo actualizar el número de teléfono: $e',
|
||||||
snackPosition: SnackPosition.BOTTOM,
|
snackPosition: SnackPosition.BOTTOM,
|
||||||
);
|
);
|
||||||
} else {
|
|
||||||
try {
|
|
||||||
final PhoneVerificationCompleted verificationCompleted =
|
|
||||||
(PhoneAuthCredential credential) async {
|
|
||||||
await currentUser?.updatePhoneNumber(credential);
|
|
||||||
Get.snackbar(
|
|
||||||
'Número de teléfono actualizado',
|
|
||||||
'El número de teléfono se ha actualizado correctamente.',
|
|
||||||
snackPosition: SnackPosition.BOTTOM,
|
|
||||||
);
|
|
||||||
};
|
|
||||||
|
|
||||||
final PhoneVerificationFailed verificationFailed =
|
|
||||||
(FirebaseAuthException e) {
|
|
||||||
Get.snackbar(
|
|
||||||
'Ingresa un numero de telefono valido',
|
|
||||||
'verifica que el campo tenga todos los caracteres o intentalo de nuevo ${e}',
|
|
||||||
snackPosition: SnackPosition.BOTTOM,
|
|
||||||
);
|
|
||||||
};
|
|
||||||
|
|
||||||
final PhoneCodeSent codeSent =
|
|
||||||
(String verificationId, [int? forceResendingToken]) {
|
|
||||||
Get.defaultDialog(
|
|
||||||
title: 'Ingrese el código de verificación',
|
|
||||||
content: Padding(
|
|
||||||
padding: const EdgeInsets.all(8.0),
|
|
||||||
child: Column(
|
|
||||||
children: [
|
|
||||||
TextField(
|
|
||||||
controller: otpCode,
|
|
||||||
decoration: InputDecoration(
|
|
||||||
labelText: 'Código de verificación',
|
|
||||||
),
|
|
||||||
),
|
|
||||||
],
|
|
||||||
),
|
|
||||||
),
|
|
||||||
actions: [
|
|
||||||
TextButton(
|
|
||||||
onPressed: () {
|
|
||||||
Get.back();
|
|
||||||
},
|
|
||||||
child: Text('Cancelar'),
|
|
||||||
),
|
|
||||||
ElevatedButton(
|
|
||||||
onPressed: () async {
|
|
||||||
try {
|
|
||||||
final PhoneAuthCredential credential =
|
|
||||||
PhoneAuthProvider.credential(
|
|
||||||
verificationId: verificationId,
|
|
||||||
smsCode: otpCode.text,
|
|
||||||
);
|
|
||||||
await currentUser?.updatePhoneNumber(credential);
|
|
||||||
Get.back();
|
|
||||||
Get.snackbar(
|
|
||||||
'Número de teléfono actualizado',
|
|
||||||
'El número de teléfono se ha actualizado correctamente.',
|
|
||||||
snackPosition: SnackPosition.BOTTOM,
|
|
||||||
);
|
|
||||||
} catch (e) {
|
|
||||||
Get.snackbar(
|
|
||||||
'Numero ya registrado',
|
|
||||||
'El numero de telefono ingresado ya se encuentra registrado',
|
|
||||||
snackPosition: SnackPosition.BOTTOM,
|
|
||||||
);
|
|
||||||
}
|
|
||||||
},
|
|
||||||
child: Text('Actualizar'),
|
|
||||||
),
|
|
||||||
],
|
|
||||||
);
|
|
||||||
};
|
|
||||||
|
|
||||||
final PhoneCodeAutoRetrievalTimeout codeAutoRetrievalTimeout =
|
|
||||||
(String verificationId) {
|
|
||||||
// Aquí puedes hacer algo si se agota el tiempo de espera para ingresar el código de verificación automáticamente.
|
|
||||||
};
|
|
||||||
|
|
||||||
await _auth.verifyPhoneNumber(
|
|
||||||
phoneNumber: newPhoneNo,
|
|
||||||
verificationCompleted: verificationCompleted,
|
|
||||||
verificationFailed: verificationFailed,
|
|
||||||
codeSent: codeSent,
|
|
||||||
codeAutoRetrievalTimeout: codeAutoRetrievalTimeout,
|
|
||||||
);
|
|
||||||
} catch (e) {
|
|
||||||
print('Error actualizando el número de teléfono: $e');
|
|
||||||
Get.snackbar(
|
|
||||||
'Error actualizando el número de teléfono',
|
|
||||||
'Ha ocurrido un error al actualizar el número de teléfono: $e',
|
|
||||||
snackPosition: SnackPosition.BOTTOM,
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -7,9 +7,10 @@ class RegisterController extends GetxController {
|
|||||||
|
|
||||||
final email = TextEditingController();
|
final email = TextEditingController();
|
||||||
final password = TextEditingController();
|
final password = TextEditingController();
|
||||||
|
final name = TextEditingController();
|
||||||
|
|
||||||
Future<void> registerUser(String email, String password) async {
|
Future<void> registerUser(String email, String password, String name) async {
|
||||||
await AuthenticationRepository.instance
|
await AuthenticationRepository.instance
|
||||||
.createUserWithEmailAndPassword(email, password);
|
.createUserWithEmailAndPassword(email, password, name);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
import 'package:cloud_firestore/cloud_firestore.dart';
|
|
||||||
import 'package:prosappco/src/models/user_model.dart';
|
import 'package:prosappco/src/models/user_model.dart';
|
||||||
|
import 'package:prosappco/src/services/api_service.dart';
|
||||||
|
|
||||||
class ChatModel {
|
class ChatModel {
|
||||||
List<MessageModel> messages;
|
List<MessageModel> messages;
|
||||||
@@ -9,99 +9,60 @@ class ChatModel {
|
|||||||
UserModel? professional;
|
UserModel? professional;
|
||||||
String id;
|
String id;
|
||||||
|
|
||||||
ChatModel(
|
ChatModel({
|
||||||
{required this.messages,
|
required this.messages,
|
||||||
required this.professional_id,
|
required this.professional_id,
|
||||||
required this.user_id,
|
required this.user_id,
|
||||||
this.user,
|
this.user,
|
||||||
this.professional,
|
this.professional,
|
||||||
required this.id});
|
required this.id,
|
||||||
|
});
|
||||||
|
|
||||||
static Future<ChatModel> fromDocumentSnapshot2(
|
static ChatModel fromJson(Map<String, dynamic> data) {
|
||||||
DocumentSnapshot<Map<String, dynamic>> snapshot,
|
final List<dynamic> msgs = data['messages'] ?? data['message'] ?? [];
|
||||||
bool fillUserModel) async {
|
final messages = msgs.map((m) {
|
||||||
|
return MessageModel(
|
||||||
|
user: m['user'] ?? m['sender_id'] ?? '',
|
||||||
|
content: m['content'] ?? m['text'] ?? '',
|
||||||
|
timestamp: m['timestamp'] != null
|
||||||
|
? DateTime.tryParse(m['timestamp'].toString()) ?? DateTime.now()
|
||||||
|
: DateTime.now(),
|
||||||
|
);
|
||||||
|
}).toList();
|
||||||
|
|
||||||
|
return ChatModel(
|
||||||
|
messages: messages,
|
||||||
|
professional_id: data['professional_id'] ?? '',
|
||||||
|
user_id: data['user_id'] ?? '',
|
||||||
|
id: data['id']?.toString() ?? '',
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
static Future<List<ChatModel>> getChatsByProId(String userId) async {
|
||||||
try {
|
try {
|
||||||
List<MessageModel> messages = [];
|
final List<dynamic> data =
|
||||||
List<dynamic> messagesData = snapshot.get('message') ?? [];
|
await ApiService.instance.get('/chat?professionalId=$userId');
|
||||||
|
return data
|
||||||
for (var data in messagesData) {
|
.map((e) => ChatModel.fromJson(e as Map<String, dynamic>))
|
||||||
messages.add(MessageModel(
|
.toList();
|
||||||
user: data['user'] ?? '',
|
|
||||||
content: data['content'] ?? '',
|
|
||||||
timestamp: (data['timestamp'] ?? '' as Timestamp).toDate(),
|
|
||||||
));
|
|
||||||
}
|
|
||||||
|
|
||||||
return ChatModel(
|
|
||||||
messages: messages,
|
|
||||||
professional_id: snapshot.get('professional_id') ?? '',
|
|
||||||
user_id: snapshot.get('user_id') ?? '',
|
|
||||||
user: fillUserModel
|
|
||||||
? await UserModel.getUser(snapshot.get('user_id') ?? '')
|
|
||||||
: null,
|
|
||||||
professional: fillUserModel
|
|
||||||
? await UserModel.getUser(snapshot.get('professional_id') ?? '')
|
|
||||||
: null,
|
|
||||||
id: snapshot.id);
|
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
print('error $e');
|
print('error getChatsByProId $e');
|
||||||
return ChatModel(messages: [], professional_id: '', user_id: '', id: '');
|
return [];
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
static ChatModel fromDocumentSnapshot(
|
static Future<List<ChatModel>> getChatsByUserId(String userId) async {
|
||||||
DocumentSnapshot<Map<String, dynamic>> snapshot,
|
|
||||||
) {
|
|
||||||
try {
|
try {
|
||||||
List<MessageModel> messages = [];
|
final List<dynamic> data =
|
||||||
List<dynamic> messagesData = snapshot.get('message') ?? [];
|
await ApiService.instance.get('/chat?userId=$userId');
|
||||||
|
return data
|
||||||
for (var data in messagesData) {
|
.map((e) => ChatModel.fromJson(e as Map<String, dynamic>))
|
||||||
messages.add(MessageModel(
|
.toList();
|
||||||
user: data['user'] ?? '',
|
|
||||||
content: data['content'] ?? '',
|
|
||||||
timestamp: (data['timestamp'] as Timestamp).toDate(),
|
|
||||||
));
|
|
||||||
}
|
|
||||||
|
|
||||||
return ChatModel(
|
|
||||||
messages: messages,
|
|
||||||
professional_id: snapshot.get('professional_id'),
|
|
||||||
user_id: snapshot.get('user_id'),
|
|
||||||
id: snapshot.id);
|
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
print('error $e');
|
print('error getChatsByUserId $e');
|
||||||
return ChatModel(messages: [], professional_id: '', user_id: '', id: '');
|
return [];
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
static Future<List<ChatModel>> getChatsByProId(String userReceived) async {
|
|
||||||
final receivedScoresQuery = FirebaseFirestore.instance
|
|
||||||
.collection('chats')
|
|
||||||
.where('professional_id', isEqualTo: userReceived);
|
|
||||||
|
|
||||||
final receivedScoresSnapshot = await receivedScoresQuery.get();
|
|
||||||
|
|
||||||
final receivedScores = await Future.wait(receivedScoresSnapshot.docs
|
|
||||||
.map((doc) async => await fromDocumentSnapshot2(doc, true))
|
|
||||||
.toList());
|
|
||||||
|
|
||||||
return receivedScores;
|
|
||||||
}
|
|
||||||
|
|
||||||
static Future<List<ChatModel>> getChatsByUserId(String userReceived) async {
|
|
||||||
final receivedScoresQuery = FirebaseFirestore.instance
|
|
||||||
.collection('chats')
|
|
||||||
.where('user_id', isEqualTo: userReceived);
|
|
||||||
|
|
||||||
final receivedScoresSnapshot = await receivedScoresQuery.get();
|
|
||||||
|
|
||||||
final receivedScores = await Future.wait(receivedScoresSnapshot.docs
|
|
||||||
.map((doc) async => await fromDocumentSnapshot2(doc, true))
|
|
||||||
.toList());
|
|
||||||
|
|
||||||
return receivedScores;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
class MessageModel {
|
class MessageModel {
|
||||||
|
|||||||
+68
-106
@@ -1,6 +1,6 @@
|
|||||||
import 'package:cloud_firestore/cloud_firestore.dart';
|
|
||||||
import 'package:prosappco/src/authentication/authentication_repository.dart';
|
import 'package:prosappco/src/authentication/authentication_repository.dart';
|
||||||
import 'package:prosappco/src/models/scores_model.dart';
|
import 'package:prosappco/src/models/scores_model.dart';
|
||||||
|
import 'package:prosappco/src/services/api_service.dart';
|
||||||
|
|
||||||
final uid = AuthenticationRepository.instance.getCurrentUserUid();
|
final uid = AuthenticationRepository.instance.getCurrentUserUid();
|
||||||
|
|
||||||
@@ -22,10 +22,7 @@ class EventoService {
|
|||||||
bool userScored,
|
bool userScored,
|
||||||
) async {
|
) async {
|
||||||
try {
|
try {
|
||||||
DateTime ahora = DateTime.now();
|
final Map<String, dynamic> result = await ApiService.instance.post('/services', {
|
||||||
|
|
||||||
final eventId =
|
|
||||||
await FirebaseFirestore.instance.collection('services').add({
|
|
||||||
'user_id': uid,
|
'user_id': uid,
|
||||||
'title': title,
|
'title': title,
|
||||||
'description': description,
|
'description': description,
|
||||||
@@ -38,41 +35,27 @@ class EventoService {
|
|||||||
'latitude': latitude,
|
'latitude': latitude,
|
||||||
'longitude': longitude,
|
'longitude': longitude,
|
||||||
'status': status,
|
'status': status,
|
||||||
'Timestamp': ahora,
|
|
||||||
'tarifa': tarifa ?? 0,
|
'tarifa': tarifa ?? 0,
|
||||||
'professional_scored': professionalScored,
|
'professional_scored': professionalScored,
|
||||||
'user_scored': userScored,
|
'user_scored': userScored,
|
||||||
}).then((value) {
|
|
||||||
FirebaseFirestore.instance.collection('users').doc(uid).update({
|
|
||||||
'services': FieldValue.arrayUnion([value.id])
|
|
||||||
});
|
|
||||||
|
|
||||||
return value.id;
|
|
||||||
});
|
});
|
||||||
|
return result['id']?.toString();
|
||||||
return eventId;
|
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
print('Evento $e');
|
print('Evento $e');
|
||||||
|
return null;
|
||||||
}
|
}
|
||||||
return null;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
Future<List<Event>> getByProId(String day) async {
|
Future<List<Event>> getByProId(String day) async {
|
||||||
try {
|
try {
|
||||||
var snapshot = await FirebaseFirestore.instance
|
final List<dynamic> data = await ApiService.instance
|
||||||
.collection('services')
|
.get('/services?day=$day&status=aprobado');
|
||||||
// .where('professional_id', isEqualTo: uid)
|
|
||||||
.where('day', isEqualTo: day.toString())
|
|
||||||
.where('status', isEqualTo: 'aprobado')
|
|
||||||
// .orderBy('Timestamp', descending: true)
|
|
||||||
.get();
|
|
||||||
|
|
||||||
List<Event> eventos = [];
|
List<Event> eventos = [];
|
||||||
for (var element in snapshot.docs) {
|
for (var element in data) {
|
||||||
final event = Event.fromJson(element.data());
|
final event = Event.fromJson(element as Map<String, dynamic>);
|
||||||
event.scoresModel = await ScoresModel.scoreTo(event.userId, false, false);
|
event.scoresModel =
|
||||||
event.id = element.id;
|
await ScoresModel.scoreTo(event.userId, false, false);
|
||||||
eventos.add(event);
|
eventos.add(event);
|
||||||
}
|
}
|
||||||
return eventos;
|
return eventos;
|
||||||
@@ -84,42 +67,36 @@ Future<List<Event>> getByProId(String day) async {
|
|||||||
|
|
||||||
Future<List<Event>> getByProIdAll(String state1, String state2) async {
|
Future<List<Event>> getByProIdAll(String state1, String state2) async {
|
||||||
try {
|
try {
|
||||||
var snapshot = await FirebaseFirestore.instance
|
final List<dynamic> data = await ApiService.instance
|
||||||
.collection('services')
|
.get('/services?professionalId=$uid&status=$state1,$state2');
|
||||||
.where('professional_id', isEqualTo: uid)
|
|
||||||
.where('status', whereIn: [state1, state2]).get();
|
|
||||||
|
|
||||||
List<Event> eventos = [];
|
List<Event> eventos = [];
|
||||||
for (var element in snapshot.docs) {
|
for (var element in data) {
|
||||||
final event = Event.fromJson(element.data());
|
final event = Event.fromJson(element as Map<String, dynamic>);
|
||||||
event.scoresModel = await ScoresModel.scoreTo(event.userId, false, false);
|
event.scoresModel =
|
||||||
event.id = element.id;
|
await ScoresModel.scoreTo(event.userId, false, false);
|
||||||
eventos.add(event);
|
eventos.add(event);
|
||||||
}
|
}
|
||||||
return eventos;
|
return eventos;
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
print('Error getByProId $e');
|
print('Error getByProIdAll $e');
|
||||||
return [];
|
return [];
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
Future<List<Event>> getByUserIdAll(String state1, String state2) async {
|
Future<List<Event>> getByUserIdAll(String state1, String state2) async {
|
||||||
try {
|
try {
|
||||||
var snapshot = await FirebaseFirestore.instance
|
final List<dynamic> data = await ApiService.instance
|
||||||
.collection('services')
|
.get('/services?userId=$uid&status=$state1,$state2');
|
||||||
.where('user_id', isEqualTo: uid)
|
|
||||||
.where('status', whereIn: [state1, state2]).get();
|
|
||||||
|
|
||||||
List<Event> eventos = [];
|
List<Event> eventos = [];
|
||||||
for (var element in snapshot.docs) {
|
for (var element in data) {
|
||||||
final event = Event.fromJson(element.data());
|
final event = Event.fromJson(element as Map<String, dynamic>);
|
||||||
event.scoresModel = await ScoresModel.scoreTo(event.userId, false, false);
|
event.scoresModel =
|
||||||
event.id = element.id;
|
await ScoresModel.scoreTo(event.userId, false, false);
|
||||||
eventos.add(event);
|
eventos.add(event);
|
||||||
}
|
}
|
||||||
return eventos;
|
return eventos;
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
print('Error getByUserId $e');
|
print('Error getByUserIdAll $e');
|
||||||
return [];
|
return [];
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -142,7 +119,7 @@ class Event {
|
|||||||
int? tarifa;
|
int? tarifa;
|
||||||
bool professionalScored;
|
bool professionalScored;
|
||||||
bool userScored;
|
bool userScored;
|
||||||
Timestamp? timeStamp;
|
DateTime? timeStamp;
|
||||||
|
|
||||||
Event({
|
Event({
|
||||||
this.id,
|
this.id,
|
||||||
@@ -165,94 +142,79 @@ class Event {
|
|||||||
});
|
});
|
||||||
|
|
||||||
factory Event.fromJson(Map<String, dynamic> json) {
|
factory Event.fromJson(Map<String, dynamic> json) {
|
||||||
|
DateTime? ts;
|
||||||
|
final raw = json['Timestamp'] ?? json['created_at'];
|
||||||
|
if (raw is String) ts = DateTime.tryParse(raw);
|
||||||
|
|
||||||
return Event(
|
return Event(
|
||||||
id: json['id'] ?? '',
|
id: json['id']?.toString() ?? '',
|
||||||
title: json['title'],
|
title: json['title'] ?? '',
|
||||||
description: json['description'],
|
description: json['description'],
|
||||||
day: json['day'],
|
day: json['day'] ?? '',
|
||||||
range1Hour1: json['range1Hour1'],
|
range1Hour1: json['range1Hour1'] ?? '',
|
||||||
range1Hour2: json['range1Hour2'],
|
range1Hour2: json['range1Hour2'],
|
||||||
userId: json['user_id'],
|
userId: json['user_id'] ?? '',
|
||||||
professionalId: json['professional_id'],
|
professionalId: json['professional_id'] ?? '',
|
||||||
ubicacion: json['ubicacion'] ?? '',
|
ubicacion: json['ubicacion'] ?? '',
|
||||||
address: json['address'] ?? '',
|
address: json['address'] ?? '',
|
||||||
longitud: json['longitude'] ?? 0,
|
longitud: (json['longitude'] ?? 0).toDouble(),
|
||||||
latitud: json['latitude'] ?? 0,
|
latitud: (json['latitude'] ?? 0).toDouble(),
|
||||||
status: json['status'],
|
status: json['status'] ?? 'pendiente',
|
||||||
timeStamp: json['Timestamp'] ?? 0,
|
timeStamp: ts,
|
||||||
tarifa: json['tarifas'] ?? 0,
|
tarifa: json['tarifa'] ?? json['tarifas'] ?? 0,
|
||||||
professionalScored: json['professional_scored'],
|
professionalScored: json['professional_scored'] ?? false,
|
||||||
userScored: json['user_scored'],
|
userScored: json['user_scored'] ?? false,
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
static Future<Event> getEventById(String uid) async {
|
static Future<Event> getEventById(String eventId) async {
|
||||||
try {
|
try {
|
||||||
final snapshot = await FirebaseFirestore.instance
|
final Map<String, dynamic> data =
|
||||||
.collection('services')
|
await ApiService.instance.get('/services/$eventId');
|
||||||
.doc(uid)
|
return Event.fromJson(data);
|
||||||
.get();
|
|
||||||
final Map<String, dynamic>? data = snapshot.data();
|
|
||||||
return Event.fromJson(data!);
|
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
print('Error getting user: $e');
|
print('Error getting event: $e');
|
||||||
return Event(
|
return Event(
|
||||||
title: '', day: '', range1Hour1: '', userId: '', professionalId: '');
|
title: '', day: '', range1Hour1: '', userId: '', professionalId: '');
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
static Future<List<Event>> getEventsAllById(String uid) async {
|
static Future<List<Event>> getEventsAllById(String proId) async {
|
||||||
try {
|
try {
|
||||||
var snapshot = await FirebaseFirestore.instance
|
final List<dynamic> data =
|
||||||
.collection('services')
|
await ApiService.instance.get('/services?professionalId=$proId');
|
||||||
.where('professional_id', isEqualTo: uid)
|
return data
|
||||||
.get();
|
.map((e) => Event.fromJson(e as Map<String, dynamic>))
|
||||||
|
.toList();
|
||||||
List<Event> eventos = [];
|
|
||||||
for (var element in snapshot.docs) {
|
|
||||||
eventos.add(Event.fromJson(element.data()));
|
|
||||||
}
|
|
||||||
return eventos;
|
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
print('Error getByProId $e');
|
print('Error getEventsAllById $e');
|
||||||
return [];
|
return [];
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
static Future<List<Event>> getEventsAllByIdAndStatus(String uid) async {
|
static Future<List<Event>> getEventsAllByIdAndStatus(String proId) async {
|
||||||
try {
|
try {
|
||||||
var snapshot = await FirebaseFirestore.instance
|
final List<dynamic> data = await ApiService.instance
|
||||||
.collection('services')
|
.get('/services?professionalId=$proId&status=aprobado,pendiente');
|
||||||
.where('professional_id', isEqualTo: uid)
|
return data
|
||||||
.where('status', whereIn: ['aprobado', 'pendiente']).get();
|
.map((e) => Event.fromJson(e as Map<String, dynamic>))
|
||||||
|
.toList();
|
||||||
List<Event> eventos = [];
|
|
||||||
for (var element in snapshot.docs) {
|
|
||||||
eventos.add(Event.fromJson(element.data()));
|
|
||||||
}
|
|
||||||
return eventos;
|
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
print('Error getByProId $e');
|
print('Error getEventsAllByIdAndStatus $e');
|
||||||
return [];
|
return [];
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
static Future<List<Event>> getEventsAllByIdStatus(
|
static Future<List<Event>> getEventsAllByIdStatus(
|
||||||
String uid, String state) async {
|
String proId, String state) async {
|
||||||
try {
|
try {
|
||||||
var snapshot = await FirebaseFirestore.instance
|
final List<dynamic> data = await ApiService.instance
|
||||||
.collection('services')
|
.get('/services?professionalId=$proId&status=$state');
|
||||||
.where('professional_id', isEqualTo: uid)
|
return data
|
||||||
.where('status', isEqualTo: state)
|
.map((e) => Event.fromJson(e as Map<String, dynamic>))
|
||||||
.get();
|
.toList();
|
||||||
|
|
||||||
List<Event> eventos = [];
|
|
||||||
for (var element in snapshot.docs) {
|
|
||||||
eventos.add(Event.fromJson(element.data()));
|
|
||||||
}
|
|
||||||
return eventos;
|
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
print('Error getByProId $e');
|
print('Error getEventsAllByIdStatus $e');
|
||||||
return [];
|
return [];
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,11 +1,9 @@
|
|||||||
import 'package:cloud_firestore/cloud_firestore.dart';
|
|
||||||
import 'package:firebase_storage/firebase_storage.dart';
|
|
||||||
import 'package:prosappco/src/authentication/authentication_repository.dart';
|
|
||||||
import 'package:prosappco/src/models/scores_model.dart';
|
import 'package:prosappco/src/models/scores_model.dart';
|
||||||
|
import 'package:prosappco/src/services/api_service.dart';
|
||||||
|
|
||||||
class Professional {
|
class Professional {
|
||||||
final String id;
|
final String id;
|
||||||
final Reference professionalRef;
|
final String? photoUrl;
|
||||||
final String name;
|
final String name;
|
||||||
final String professionName;
|
final String professionName;
|
||||||
final String cityName;
|
final String cityName;
|
||||||
@@ -20,7 +18,7 @@ class Professional {
|
|||||||
|
|
||||||
Professional({
|
Professional({
|
||||||
required this.id,
|
required this.id,
|
||||||
required this.professionalRef,
|
this.photoUrl,
|
||||||
required this.name,
|
required this.name,
|
||||||
required this.professionName,
|
required this.professionName,
|
||||||
required this.cityName,
|
required this.cityName,
|
||||||
@@ -40,48 +38,39 @@ class Professional {
|
|||||||
|
|
||||||
@override
|
@override
|
||||||
String toString() {
|
String toString() {
|
||||||
return 'Professional { professionalRef: $professionalRef, name: $name, professionName: $professionName, cityName: $cityName, ubicacion: $ubicacion, realAddress: $realAddress, latitude: $latitude, longitude: $longitude, professionalEspecializado: ${getEspecializaciones()} tarifa: $tarifa token: $token, }';
|
return 'Professional { photoUrl: $photoUrl, name: $name, professionName: $professionName, cityName: $cityName }';
|
||||||
}
|
}
|
||||||
|
|
||||||
static Future<Professional?> getProfessional(String uid) async {
|
static Future<Professional?> getProfessional(String uid) async {
|
||||||
var photo = '...';
|
|
||||||
final FirebaseStorage storage = FirebaseStorage.instance;
|
|
||||||
|
|
||||||
try {
|
try {
|
||||||
DocumentSnapshot user =
|
final Map<String, dynamic> data =
|
||||||
await FirebaseFirestore.instance.collection('users').doc(uid).get();
|
await ApiService.instance.get('/professionals/$uid');
|
||||||
|
|
||||||
Map<String, dynamic> data = user.data() as Map<String, dynamic>;
|
final List<String> especializaciones =
|
||||||
|
((data['especialidades'] ?? data['specialties'] ?? []) as List<dynamic>)
|
||||||
|
.map((e) => e.toString())
|
||||||
|
.toList();
|
||||||
|
|
||||||
photo = await AuthenticationRepository.instance.getPhoto(user.id);
|
return Professional(
|
||||||
|
id: uid,
|
||||||
if (data['estado'] == 'activo') {
|
photoUrl: data['picture'] ?? data['photo_url'],
|
||||||
List<String> especializaciones;
|
name: data['name'] ?? '',
|
||||||
|
professionName: data['profession'] ?? data['profesion'] ?? '',
|
||||||
especializaciones = (data['especializaciones'] as List<dynamic>)
|
cityName: data['city'] ?? '',
|
||||||
.map((e) => e.toString())
|
professionalEspecializado: especializaciones,
|
||||||
.toList();
|
ubicacion: data['ubicacion'] ?? '',
|
||||||
|
realAddress: data['address'] ?? '',
|
||||||
Professional professional = Professional(
|
latitude: (data['latitude'] ?? 0).toDouble(),
|
||||||
id: user.id,
|
longitude: (data['longitude'] ?? 0).toDouble(),
|
||||||
name: data['name'],
|
scores: await ScoresModel.scoreTo(uid, true, false),
|
||||||
professionName: data['profesion'],
|
tarifa: data['rate'] != null
|
||||||
cityName: data['city'],
|
? double.tryParse(data['rate'].toString())?.toInt()
|
||||||
professionalRef: storage.ref().child(photo),
|
: data['tarifas'],
|
||||||
professionalEspecializado: especializaciones,
|
token: data['token'],
|
||||||
ubicacion: data['ubicacion'] ?? '',
|
);
|
||||||
realAddress: data['address'] ?? '',
|
|
||||||
latitude: data['latitude'] ?? 0,
|
|
||||||
longitude: data['longitude'] ?? 0,
|
|
||||||
scores: await ScoresModel.scoreFrom(uid, true, true),
|
|
||||||
tarifa: data['tarifas'] ?? 0,
|
|
||||||
token: data['token'] ?? '',
|
|
||||||
);
|
|
||||||
return professional;
|
|
||||||
}
|
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
print('Error al obtener profesionales: $e');
|
print('Error al obtener profesional: $e');
|
||||||
|
return null;
|
||||||
}
|
}
|
||||||
return null;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,6 +1,4 @@
|
|||||||
import 'package:cloud_firestore/cloud_firestore.dart';
|
import 'package:prosappco/src/services/api_service.dart';
|
||||||
import 'package:firebase_storage/firebase_storage.dart';
|
|
||||||
import 'package:prosappco/src/models/user_model.dart';
|
|
||||||
|
|
||||||
class ScoresModel {
|
class ScoresModel {
|
||||||
late int total;
|
late int total;
|
||||||
@@ -18,47 +16,30 @@ class ScoresModel {
|
|||||||
}
|
}
|
||||||
|
|
||||||
double averageScore(List<ScoreDetailModel> details) {
|
double averageScore(List<ScoreDetailModel> details) {
|
||||||
if (details.isEmpty) {
|
if (details.isEmpty) return 0.0;
|
||||||
return 0.0;
|
final sum = details.map((d) => d.score).reduce((a, b) => a + b);
|
||||||
}
|
|
||||||
final sum = details.map((detail) => detail.score).reduce((a, b) => a + b);
|
|
||||||
return sum / details.length;
|
return sum / details.length;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Me
|
|
||||||
static Future<ScoresModel> scoreTo(
|
static Future<ScoresModel> scoreTo(
|
||||||
String? userReceived, bool isFromClient, bool userInfo) async {
|
String? userId, bool isFromClient, bool userInfo) async {
|
||||||
final receivedScoresQuery = FirebaseFirestore.instance
|
if (userId == null || userId.isEmpty) return ScoresModel([]);
|
||||||
.collection('scores')
|
try {
|
||||||
.where('is_from_professional', isEqualTo: isFromClient)
|
final List<dynamic> data =
|
||||||
.where('to_user', isEqualTo: userReceived);
|
await ApiService.instance.get('/comments/reputation/$userId');
|
||||||
|
final details =
|
||||||
final receivedScoresSnapshot = await receivedScoresQuery.get();
|
data.map((e) => ScoreDetailModel.fromJson(e as Map<String, dynamic>)).toList();
|
||||||
|
return ScoresModel(details);
|
||||||
final receivedScores = await Future.wait(receivedScoresSnapshot.docs
|
} catch (e) {
|
||||||
.map((doc) async =>
|
print('Error scoreTo: $e');
|
||||||
await ScoreDetailModel.fromDocumentSnapshot(doc, userInfo))
|
return ScoresModel([]);
|
||||||
.toList());
|
}
|
||||||
|
|
||||||
return ScoresModel(receivedScores);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// You
|
// ponytail: scoreFrom and scoreTo now both hit the same reputation endpoint
|
||||||
static Future<ScoresModel> scoreFrom(
|
static Future<ScoresModel> scoreFrom(
|
||||||
String userGiven, bool isFromClient, bool userInfo) async {
|
String userId, bool isFromClient, bool userInfo) async {
|
||||||
final givenScoresQuery = FirebaseFirestore.instance
|
return scoreTo(userId, isFromClient, userInfo);
|
||||||
.collection('scores')
|
|
||||||
.where('is_from_professional', isEqualTo: isFromClient)
|
|
||||||
.where('from_user', isEqualTo: userGiven);
|
|
||||||
|
|
||||||
final givenScoresSnapshot = await givenScoresQuery.get();
|
|
||||||
|
|
||||||
final givenScores = await Future.wait(givenScoresSnapshot.docs
|
|
||||||
.map((doc) async =>
|
|
||||||
await ScoreDetailModel.fromDocumentSnapshot(doc, userInfo))
|
|
||||||
.toList());
|
|
||||||
|
|
||||||
return ScoresModel(givenScores);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -69,9 +50,8 @@ class ScoreDetailModel {
|
|||||||
final String toUser;
|
final String toUser;
|
||||||
final String comment;
|
final String comment;
|
||||||
final bool isFromClient;
|
final bool isFromClient;
|
||||||
|
|
||||||
final String name;
|
final String name;
|
||||||
final Reference? avatar;
|
final String? avatar;
|
||||||
|
|
||||||
ScoreDetailModel({
|
ScoreDetailModel({
|
||||||
required this.id,
|
required this.id,
|
||||||
@@ -81,32 +61,24 @@ class ScoreDetailModel {
|
|||||||
required this.comment,
|
required this.comment,
|
||||||
required this.isFromClient,
|
required this.isFromClient,
|
||||||
required this.name,
|
required this.name,
|
||||||
required this.avatar,
|
this.avatar,
|
||||||
});
|
});
|
||||||
|
|
||||||
|
factory ScoreDetailModel.fromJson(Map<String, dynamic> data) {
|
||||||
|
return ScoreDetailModel(
|
||||||
|
id: data['id']?.toString() ?? '',
|
||||||
|
score: double.tryParse(data['score']?.toString() ?? '0') ?? 0,
|
||||||
|
fromUser: data['from_user'] ?? '',
|
||||||
|
toUser: data['to_user'] ?? '',
|
||||||
|
comment: data['comment'] ?? '',
|
||||||
|
isFromClient: data['is_from_professional'] ?? false,
|
||||||
|
name: data['from_user_name'] ?? data['name'] ?? '...',
|
||||||
|
avatar: data['avatar'],
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
@override
|
@override
|
||||||
String toString() {
|
String toString() {
|
||||||
return 'ScoreDetailModel{id: $id, score: $score, fromUser: $fromUser, toUser: $toUser, comment: $comment, isFromClient: $isFromClient, name: $name, avatar: $avatar}';
|
return 'ScoreDetailModel{id: $id, score: $score, fromUser: $fromUser, name: $name}';
|
||||||
}
|
|
||||||
|
|
||||||
static Future<ScoreDetailModel> fromDocumentSnapshot(
|
|
||||||
DocumentSnapshot<Map<String, dynamic>> snapshot, bool userInfo) async {
|
|
||||||
try {
|
|
||||||
final Map<String, dynamic> data = snapshot.data()!;
|
|
||||||
final user = userInfo ? await UserModel.getUser(data['from_user']) : null;
|
|
||||||
|
|
||||||
return ScoreDetailModel(
|
|
||||||
id: snapshot.id,
|
|
||||||
score: double.parse(data['score'].toString()),
|
|
||||||
fromUser: data['from_user'],
|
|
||||||
toUser: data['to_user'],
|
|
||||||
comment: data['comment'],
|
|
||||||
isFromClient: data['is_from_professional'],
|
|
||||||
name: user?.name ?? "...",
|
|
||||||
avatar: user?.photo);
|
|
||||||
} catch (e) {
|
|
||||||
print('error en score $e');
|
|
||||||
rethrow;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
import 'package:cloud_firestore/cloud_firestore.dart';
|
import 'package:prosappco/src/services/api_service.dart';
|
||||||
|
|
||||||
class SettingModel {
|
class SettingModel {
|
||||||
final bool domicilios;
|
final bool domicilios;
|
||||||
@@ -29,25 +29,12 @@ class SettingModel {
|
|||||||
this.terminosCondiciones,
|
this.terminosCondiciones,
|
||||||
);
|
);
|
||||||
|
|
||||||
static Future<SettingModel> fromJson(Map<String, dynamic>? json) async {
|
static SettingModel _empty() =>
|
||||||
try {
|
SettingModel(false, false, false, '', '', '', '', '', '', '', '', '');
|
||||||
if (json == null) {
|
|
||||||
return SettingModel(
|
|
||||||
false,
|
|
||||||
false,
|
|
||||||
false,
|
|
||||||
'',
|
|
||||||
'',
|
|
||||||
'',
|
|
||||||
'',
|
|
||||||
'',
|
|
||||||
'',
|
|
||||||
'',
|
|
||||||
'',
|
|
||||||
'',
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
|
static Future<SettingModel> fromJson(Map<String, dynamic>? json) async {
|
||||||
|
if (json == null) return _empty();
|
||||||
|
try {
|
||||||
return SettingModel(
|
return SettingModel(
|
||||||
json['domicilios'] ?? false,
|
json['domicilios'] ?? false,
|
||||||
json['google'] ?? false,
|
json['google'] ?? false,
|
||||||
@@ -58,50 +45,30 @@ class SettingModel {
|
|||||||
json['email_soporte'] ?? '',
|
json['email_soporte'] ?? '',
|
||||||
json['dias_soporte'] ?? '',
|
json['dias_soporte'] ?? '',
|
||||||
json['horas_soporte'] ?? '',
|
json['horas_soporte'] ?? '',
|
||||||
json['version'] ?? '', // Asegúrate de manejar nulos aquí
|
json['version'] ?? '',
|
||||||
json['politicas_privacidad'] ?? '',
|
json['politicas_privacidad'] ?? '',
|
||||||
json['terminos_condiciones'] ?? '',
|
json['terminos_condiciones'] ?? '',
|
||||||
);
|
);
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
print('Error settings: $e');
|
print('Error settings: $e');
|
||||||
return SettingModel(
|
return _empty();
|
||||||
false,
|
|
||||||
false,
|
|
||||||
false,
|
|
||||||
'',
|
|
||||||
'',
|
|
||||||
'',
|
|
||||||
'',
|
|
||||||
'',
|
|
||||||
'',
|
|
||||||
'',
|
|
||||||
'',
|
|
||||||
'',
|
|
||||||
);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
static Future<SettingModel> getSettings() async {
|
static Future<SettingModel> getSettings() async {
|
||||||
try {
|
try {
|
||||||
final DocumentSnapshot<Map<String, dynamic>> snapshot =
|
final Map<String, dynamic> data =
|
||||||
await FirebaseFirestore.instance
|
await ApiService.instance.get('/settings');
|
||||||
.collection('settings')
|
return fromJson(data);
|
||||||
.doc('global')
|
|
||||||
.get();
|
|
||||||
|
|
||||||
final Map<String, dynamic>? data = snapshot.data();
|
|
||||||
|
|
||||||
return fromJson(data!);
|
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
print('Error getting settings: $e');
|
print('Error getting settings: $e');
|
||||||
return SettingModel(
|
return _empty();
|
||||||
false, false, false, '', '', '', '', '', '', '', '', '');
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@override
|
@override
|
||||||
String toString() {
|
String toString() {
|
||||||
return 'SettingModel { domicilios: $domicilios,google: $google, tarifas: $tarifas, '
|
return 'SettingModel { domicilios: $domicilios, google: $google, tarifas: $tarifas, '
|
||||||
'titulo: $titulo, parrafo: $parrafo, numero: $numero, '
|
'titulo: $titulo, parrafo: $parrafo, numero: $numero, '
|
||||||
'email: $email, dias: $dias, horas: $horas, '
|
'email: $email, dias: $dias, horas: $horas, '
|
||||||
'version: $version, politicasPrivacidad: $proliticasPrivacidad, '
|
'version: $version, politicasPrivacidad: $proliticasPrivacidad, '
|
||||||
|
|||||||
@@ -1,75 +1,53 @@
|
|||||||
import 'package:cloud_firestore/cloud_firestore.dart';
|
|
||||||
import 'package:firebase_storage/firebase_storage.dart';
|
|
||||||
import 'package:prosappco/src/presentation/screens/professional.dart';
|
|
||||||
|
|
||||||
class UserModel {
|
class UserModel {
|
||||||
|
final String id;
|
||||||
final String name;
|
final String name;
|
||||||
final String city;
|
final String city;
|
||||||
final String? profession;
|
final String? profession;
|
||||||
final String? state;
|
final int proState;
|
||||||
final Reference? photo;
|
final String? picture;
|
||||||
|
final String? banner;
|
||||||
final int? tarifa;
|
final int? tarifa;
|
||||||
final String? phoneNumber;
|
final String? phoneNumber;
|
||||||
final String? token;
|
final String? email;
|
||||||
|
final String? gender;
|
||||||
|
final String? birthday;
|
||||||
|
|
||||||
UserModel(this.name, this.city, this.profession, this.state, this.photo,
|
UserModel({
|
||||||
this.tarifa, this.phoneNumber, this.token);
|
required this.id,
|
||||||
|
required this.name,
|
||||||
|
required this.city,
|
||||||
|
this.profession,
|
||||||
|
this.proState = 0,
|
||||||
|
this.picture,
|
||||||
|
this.banner,
|
||||||
|
this.tarifa,
|
||||||
|
this.phoneNumber,
|
||||||
|
this.email,
|
||||||
|
this.gender,
|
||||||
|
this.birthday,
|
||||||
|
});
|
||||||
|
|
||||||
static Future<UserModel> fromJson(
|
factory UserModel.fromApi(Map<String, dynamic> json) {
|
||||||
Map<String, dynamic>? json, String uid) async {
|
final professional = json['professionals'] as Map<String, dynamic>?;
|
||||||
try {
|
return UserModel(
|
||||||
if (json == null) return UserModel('', '', '', null, null, 0, '', '');
|
id: json['id'] ?? '',
|
||||||
|
name: json['name'] ?? 'Sin nombre',
|
||||||
String? avatar = json['photo'];
|
city: json['city'] ?? '',
|
||||||
return UserModel(
|
profession: professional?['profession'],
|
||||||
json['name'],
|
proState: json['pro_state'] ?? 0,
|
||||||
json['city'],
|
picture: json['picture'],
|
||||||
json['profesion'],
|
banner: professional?['banner_picture'],
|
||||||
json['estado'],
|
tarifa: professional?['rate'] != null
|
||||||
avatar != null ? storage.ref().child(avatar) : null,
|
? double.tryParse(professional!['rate'].toString())?.toInt()
|
||||||
json['tarifas'] ?? 0,
|
: null,
|
||||||
json['phoneNumber'],
|
phoneNumber: json['phone'],
|
||||||
json['token'],
|
email: json['email'],
|
||||||
);
|
gender: json['gender'],
|
||||||
} catch (e) {
|
birthday: json['birthday'],
|
||||||
print('$e');
|
);
|
||||||
return UserModel('', '', '', null, null, 0, '', '');
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@override
|
@override
|
||||||
String toString() {
|
String toString() =>
|
||||||
return 'UserModel(name: $name, city: $city, profession: $profession, state: $state, tarifa: $tarifa, phoneNumber: $phoneNumber, token: $token)';
|
'UserModel(id: $id, name: $name, city: $city, profession: $profession, proState: $proState)';
|
||||||
}
|
|
||||||
|
|
||||||
static UserModel fromFirestore(Map<String, dynamic> firestoreMap) {
|
|
||||||
try {
|
|
||||||
String? avatar = firestoreMap['photo'];
|
|
||||||
return UserModel(
|
|
||||||
firestoreMap['name'] ?? 'Sin nombre',
|
|
||||||
firestoreMap['city'] ?? 'Sin ciudad',
|
|
||||||
firestoreMap['profesion'],
|
|
||||||
firestoreMap['estado'],
|
|
||||||
avatar != null ? storage.ref().child(avatar) : null,
|
|
||||||
firestoreMap['tarifas'] ?? 0,
|
|
||||||
firestoreMap['phoneNumber'] ?? 'Sin numero',
|
|
||||||
firestoreMap['token'],
|
|
||||||
);
|
|
||||||
} catch (e) {
|
|
||||||
print('DesdeProvider $e');
|
|
||||||
return UserModel('', '', '', null, null, 0, '', '');
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
static Future<UserModel> getUser(String uid) async {
|
|
||||||
try {
|
|
||||||
final snapshot =
|
|
||||||
await FirebaseFirestore.instance.collection('users').doc(uid).get();
|
|
||||||
final Map<String, dynamic>? data = snapshot.data();
|
|
||||||
return fromJson(data, uid);
|
|
||||||
} catch (e) {
|
|
||||||
print('Error getting user: $e');
|
|
||||||
return UserModel('', '', '', null, null, 0, '', '');
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,6 +1,3 @@
|
|||||||
import 'dart:convert';
|
|
||||||
|
|
||||||
import 'package:cloud_firestore/cloud_firestore.dart';
|
|
||||||
import 'package:flutter/cupertino.dart';
|
import 'package:flutter/cupertino.dart';
|
||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
import 'package:intl/intl.dart';
|
import 'package:intl/intl.dart';
|
||||||
@@ -10,9 +7,8 @@ import 'package:prosappco/src/components/pop_appbar.dart';
|
|||||||
import 'package:prosappco/src/models/chat_model.dart';
|
import 'package:prosappco/src/models/chat_model.dart';
|
||||||
import 'package:prosappco/src/models/event_model.dart';
|
import 'package:prosappco/src/models/event_model.dart';
|
||||||
import 'package:prosappco/src/models/professional_model.dart';
|
import 'package:prosappco/src/models/professional_model.dart';
|
||||||
import 'package:prosappco/src/models/user_model.dart';
|
|
||||||
import 'package:prosappco/src/presentation/screens/professional_info.dart';
|
import 'package:prosappco/src/presentation/screens/professional_info.dart';
|
||||||
import 'package:http/http.dart' as http;
|
import 'package:prosappco/src/services/api_service.dart';
|
||||||
|
|
||||||
class ChatScreen extends StatefulWidget {
|
class ChatScreen extends StatefulWidget {
|
||||||
final String? eventoId;
|
final String? eventoId;
|
||||||
@@ -25,59 +21,98 @@ class ChatScreen extends StatefulWidget {
|
|||||||
class _ChatScreenState extends State<ChatScreen> {
|
class _ChatScreenState extends State<ChatScreen> {
|
||||||
final _textController = TextEditingController();
|
final _textController = TextEditingController();
|
||||||
final uid = AuthenticationRepository.instance.getCurrentUserUid();
|
final uid = AuthenticationRepository.instance.getCurrentUserUid();
|
||||||
UserModel? user;
|
|
||||||
|
// Other user info
|
||||||
|
String otherName = '';
|
||||||
|
String? otherPhoto;
|
||||||
|
String? otherProfession;
|
||||||
Professional? professional;
|
Professional? professional;
|
||||||
bool pro = false;
|
bool pro = false;
|
||||||
|
|
||||||
Future<void> sendPushNotification(String token) async {
|
// Chat state
|
||||||
try {
|
String? chatId;
|
||||||
http.Response response = await http.post(
|
List<MessageModel> messages = [];
|
||||||
Uri.parse('https://fcm.googleapis.com/fcm/send'),
|
bool loading = true;
|
||||||
headers: <String, String>{
|
|
||||||
'Content-Type': 'application/json; charset=UTF-8',
|
|
||||||
'Authorization':
|
|
||||||
'key=AAAAORdR-xU:APA91bF_wblg86jHAC-uexrXPHavYRlk5wge1Gf46m56V4J2D2L37Cp_hf46JZUzpvsWPSpqc5ewHelKI9LTifUG_s2mciMI6e5VLKo7E1R8btbNo7iaM9do2ctoyHKUm1atlZBdKaN2',
|
|
||||||
},
|
|
||||||
body: jsonEncode(
|
|
||||||
<String, dynamic>{
|
|
||||||
'notification': <String, dynamic>{
|
|
||||||
'body': 'Tienes un nuevo mensaje',
|
|
||||||
'title': 'Nuevo mensaje',
|
|
||||||
},
|
|
||||||
'priority': 'high',
|
|
||||||
'data': <String, dynamic>{
|
|
||||||
'click_action': 'FLUTTER_NOTIFICATION_CLICK',
|
|
||||||
'id': '1',
|
|
||||||
'status': 'done'
|
|
||||||
},
|
|
||||||
'to': token,
|
|
||||||
},
|
|
||||||
),
|
|
||||||
);
|
|
||||||
response;
|
|
||||||
} catch (e) {
|
|
||||||
print('error al enviar notificacion $e');
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@override
|
@override
|
||||||
void initState() {
|
void initState() {
|
||||||
super.initState();
|
super.initState();
|
||||||
Event.getEventById(widget.eventoId!).then((event) {
|
_init();
|
||||||
if (user == null) {
|
}
|
||||||
if (uid != event.userId) {
|
|
||||||
UserModel.getUser(event.userId).then(
|
Future<void> _init() async {
|
||||||
(UserModel s) => setState(() => user = s),
|
try {
|
||||||
);
|
final event = await Event.getEventById(widget.eventoId!);
|
||||||
} else {
|
|
||||||
Professional.getProfessional(event.professionalId)
|
final isClient = uid == event.userId;
|
||||||
.then((value) => {professional = value});
|
final otherUserId =
|
||||||
UserModel.getUser(event.professionalId).then(
|
isClient ? event.professionalId : event.userId;
|
||||||
(UserModel s) => setState(() => {user = s, pro = true}),
|
pro = isClient;
|
||||||
);
|
|
||||||
}
|
// Load other user info
|
||||||
|
final Map<String, dynamic> userData =
|
||||||
|
await ApiService.instance.get('/users/$otherUserId');
|
||||||
|
if (mounted) {
|
||||||
|
setState(() {
|
||||||
|
otherName = userData['name'] ?? '';
|
||||||
|
otherPhoto = userData['picture'];
|
||||||
|
otherProfession = userData['profession'];
|
||||||
|
});
|
||||||
}
|
}
|
||||||
});
|
|
||||||
|
if (isClient) {
|
||||||
|
professional = await Professional.getProfessional(event.professionalId);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Start/get chat
|
||||||
|
final Map<String, dynamic> chatData = await ApiService.instance
|
||||||
|
.post('/chat/start/${event.professionalId}', {});
|
||||||
|
chatId = chatData['id']?.toString() ?? chatData['chatId']?.toString();
|
||||||
|
|
||||||
|
await _loadMessages();
|
||||||
|
} catch (e) {
|
||||||
|
print('Error initializing chat: $e');
|
||||||
|
if (mounted) setState(() => loading = false);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Future<void> _loadMessages() async {
|
||||||
|
if (chatId == null) return;
|
||||||
|
try {
|
||||||
|
final List<dynamic> data =
|
||||||
|
await ApiService.instance.get('/chat/$chatId/messages');
|
||||||
|
if (mounted) {
|
||||||
|
setState(() {
|
||||||
|
messages = data.map((m) {
|
||||||
|
return MessageModel(
|
||||||
|
user: m['sender_id'] ?? m['user'] ?? '',
|
||||||
|
content: m['content'] ?? m['text'] ?? '',
|
||||||
|
timestamp: m['created_at'] != null
|
||||||
|
? DateTime.tryParse(m['created_at'].toString()) ??
|
||||||
|
DateTime.now()
|
||||||
|
: DateTime.now(),
|
||||||
|
);
|
||||||
|
}).toList();
|
||||||
|
loading = false;
|
||||||
|
});
|
||||||
|
}
|
||||||
|
} catch (e) {
|
||||||
|
print('Error loading messages: $e');
|
||||||
|
if (mounted) setState(() => loading = false);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Future<void> _sendMessage() async {
|
||||||
|
final text = _textController.text.trim();
|
||||||
|
if (text.isEmpty || chatId == null) return;
|
||||||
|
_textController.clear();
|
||||||
|
try {
|
||||||
|
await ApiService.instance
|
||||||
|
.post('/chat/$chatId/message', {'content': text});
|
||||||
|
await _loadMessages();
|
||||||
|
} catch (e) {
|
||||||
|
print('Error sending message: $e');
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@override
|
@override
|
||||||
@@ -108,7 +143,7 @@ class _ChatScreenState extends State<ChatScreen> {
|
|||||||
child: ListTile(
|
child: ListTile(
|
||||||
leading: GestureDetector(
|
leading: GestureDetector(
|
||||||
onTap: () {
|
onTap: () {
|
||||||
if (pro) {
|
if (pro && professional != null) {
|
||||||
Navigator.of(context).push(
|
Navigator.of(context).push(
|
||||||
CupertinoPageRoute(
|
CupertinoPageRoute(
|
||||||
builder: (BuildContext context) {
|
builder: (BuildContext context) {
|
||||||
@@ -123,28 +158,30 @@ class _ChatScreenState extends State<ChatScreen> {
|
|||||||
child: Padding(
|
child: Padding(
|
||||||
padding: const EdgeInsets.symmetric(horizontal: 10),
|
padding: const EdgeInsets.symmetric(horizontal: 10),
|
||||||
child: ReferencePhoto(
|
child: ReferencePhoto(
|
||||||
ref: user?.photo,
|
ref: otherPhoto,
|
||||||
size: 55,
|
size: 55,
|
||||||
sizeCircle: 60,
|
sizeCircle: 60,
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
title: Text(
|
title: Text(
|
||||||
'${user?.name}',
|
otherName,
|
||||||
style: const TextStyle(
|
style: const TextStyle(
|
||||||
color: Colors.black, fontWeight: FontWeight.w600),
|
color: Colors.black, fontWeight: FontWeight.w600),
|
||||||
),
|
),
|
||||||
subtitle: Text(user?.profession ?? ''),
|
subtitle: Text(otherProfession ?? ''),
|
||||||
trailing: const Icon(Icons.keyboard_arrow_right),
|
trailing: const Icon(Icons.keyboard_arrow_right),
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
Expanded(
|
Expanded(
|
||||||
child: SingleChildScrollView(
|
child: loading
|
||||||
padding: const EdgeInsets.only(top: 10),
|
? const Center(child: CircularProgressIndicator())
|
||||||
reverse: true,
|
: SingleChildScrollView(
|
||||||
child: streamB(uid!),
|
padding: const EdgeInsets.only(top: 10),
|
||||||
),
|
reverse: true,
|
||||||
|
child: _messageList(),
|
||||||
|
),
|
||||||
),
|
),
|
||||||
Container(
|
Container(
|
||||||
alignment: Alignment.bottomCenter,
|
alignment: Alignment.bottomCenter,
|
||||||
@@ -175,104 +212,12 @@ class _ChatScreenState extends State<ChatScreen> {
|
|||||||
filled: true,
|
filled: true,
|
||||||
fillColor: Colors.grey[200],
|
fillColor: Colors.grey[200],
|
||||||
),
|
),
|
||||||
onFieldSubmitted: (value) async {
|
onFieldSubmitted: (value) => _sendMessage(),
|
||||||
String muestra = _textController.text.trim();
|
|
||||||
if (muestra.isNotEmpty) {
|
|
||||||
final nuevoMensaje = MessageModel(
|
|
||||||
user: uid!,
|
|
||||||
content:
|
|
||||||
_textController.text.trimLeft().trimRight(),
|
|
||||||
timestamp: DateTime.now());
|
|
||||||
|
|
||||||
final nuevoMensajeMap = {
|
|
||||||
'user': nuevoMensaje.user,
|
|
||||||
'content': nuevoMensaje.content,
|
|
||||||
'timestamp': nuevoMensaje.timestamp,
|
|
||||||
};
|
|
||||||
|
|
||||||
FirebaseFirestore.instance
|
|
||||||
.collection('chats')
|
|
||||||
.doc(widget.eventoId)
|
|
||||||
.update({
|
|
||||||
'message': FieldValue.arrayUnion([nuevoMensajeMap])
|
|
||||||
});
|
|
||||||
|
|
||||||
if (user?.token != '') {
|
|
||||||
sendPushNotification(user!.token!);
|
|
||||||
}
|
|
||||||
|
|
||||||
// if (user?.token != '') {
|
|
||||||
// final mensajesQuerySnapshot =
|
|
||||||
// await FirebaseFirestore.instance
|
|
||||||
// .collection('chats')
|
|
||||||
// .doc(widget.eventoId)
|
|
||||||
// .get();
|
|
||||||
// final mensajes =
|
|
||||||
// mensajesQuerySnapshot.data()?['message'];
|
|
||||||
// if (mensajes != null && mensajes.isNotEmpty) {
|
|
||||||
// final ultimoMensaje = mensajes.last;
|
|
||||||
// final ultimoMensajeUser = ultimoMensaje['user'];
|
|
||||||
// if (ultimoMensajeUser == uid) {
|
|
||||||
// // El último mensaje fue enviado por ti, no se envía la notificación
|
|
||||||
// } else {
|
|
||||||
// sendPushNotification(user!.token!);
|
|
||||||
// }
|
|
||||||
// } else {
|
|
||||||
// sendPushNotification(user!.token!);
|
|
||||||
// }
|
|
||||||
// }
|
|
||||||
_textController.clear();
|
|
||||||
}
|
|
||||||
},
|
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
const SizedBox(width: 12),
|
const SizedBox(width: 12),
|
||||||
GestureDetector(
|
GestureDetector(
|
||||||
onTap: () async {
|
onTap: _sendMessage,
|
||||||
String muestra = _textController.text.trim();
|
|
||||||
if (muestra.isNotEmpty) {
|
|
||||||
final nuevoMensaje = MessageModel(
|
|
||||||
user: uid!,
|
|
||||||
content:
|
|
||||||
_textController.text.trimLeft().trimRight(),
|
|
||||||
timestamp: DateTime.now());
|
|
||||||
|
|
||||||
final nuevoMensajeMap = {
|
|
||||||
'user': nuevoMensaje.user,
|
|
||||||
'content': nuevoMensaje.content,
|
|
||||||
'timestamp': nuevoMensaje.timestamp,
|
|
||||||
};
|
|
||||||
|
|
||||||
FirebaseFirestore.instance
|
|
||||||
.collection('chats')
|
|
||||||
.doc(widget.eventoId)
|
|
||||||
.update({
|
|
||||||
'message': FieldValue.arrayUnion([nuevoMensajeMap])
|
|
||||||
});
|
|
||||||
|
|
||||||
if (user?.token != '') {
|
|
||||||
final mensajesQuerySnapshot = await FirebaseFirestore
|
|
||||||
.instance
|
|
||||||
.collection('chats')
|
|
||||||
.doc(widget.eventoId)
|
|
||||||
.get();
|
|
||||||
final mensajes =
|
|
||||||
mensajesQuerySnapshot.data()?['message'];
|
|
||||||
if (mensajes != null && mensajes.isNotEmpty) {
|
|
||||||
final ultimoMensaje = mensajes.last;
|
|
||||||
final ultimoMensajeUser = ultimoMensaje['user'];
|
|
||||||
if (ultimoMensajeUser == uid) {
|
|
||||||
} else {
|
|
||||||
sendPushNotification(user!.token!);
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
sendPushNotification(user!.token!);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
_textController.clear();
|
|
||||||
}
|
|
||||||
},
|
|
||||||
child: Container(
|
child: Container(
|
||||||
height: 50,
|
height: 50,
|
||||||
width: 50,
|
width: 50,
|
||||||
@@ -281,10 +226,7 @@ class _ChatScreenState extends State<ChatScreen> {
|
|||||||
borderRadius: BorderRadius.circular(30),
|
borderRadius: BorderRadius.circular(30),
|
||||||
),
|
),
|
||||||
child: const Center(
|
child: const Center(
|
||||||
child: Icon(
|
child: Icon(Icons.send, color: Colors.white),
|
||||||
Icons.send,
|
|
||||||
color: Colors.white,
|
|
||||||
),
|
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
)
|
)
|
||||||
@@ -297,100 +239,81 @@ class _ChatScreenState extends State<ChatScreen> {
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
StreamBuilder<DocumentSnapshot<Map<String, dynamic>>> streamB(String uid) {
|
Widget _messageList() {
|
||||||
return StreamBuilder(
|
return Column(
|
||||||
stream: FirebaseFirestore.instance
|
children: [
|
||||||
.collection('chats')
|
...messages.map(
|
||||||
.doc(widget.eventoId)
|
(e) => uid != e.user
|
||||||
.snapshots(),
|
? ListTile(
|
||||||
builder: (context, snapshot) {
|
title: Column(
|
||||||
if (!snapshot.hasData) {
|
mainAxisAlignment: MainAxisAlignment.start,
|
||||||
return const Center(child: CircularProgressIndicator());
|
crossAxisAlignment: CrossAxisAlignment.start,
|
||||||
}
|
children: [
|
||||||
|
Container(
|
||||||
final data = snapshot.data!;
|
margin: const EdgeInsets.only(right: 60),
|
||||||
final chat = ChatModel.fromDocumentSnapshot(data);
|
padding: const EdgeInsets.symmetric(
|
||||||
|
vertical: 10, horizontal: 16),
|
||||||
return Column(
|
decoration: BoxDecoration(
|
||||||
children: [
|
color: Colors.grey.shade200,
|
||||||
...chat.messages.map(
|
borderRadius: const BorderRadius.only(
|
||||||
(e) => uid != e.user
|
topRight: Radius.circular(20),
|
||||||
? ListTile(
|
bottomLeft: Radius.circular(20),
|
||||||
title: Column(
|
bottomRight: Radius.circular(20),
|
||||||
mainAxisAlignment: MainAxisAlignment.start,
|
|
||||||
crossAxisAlignment: CrossAxisAlignment.start,
|
|
||||||
children: [
|
|
||||||
Container(
|
|
||||||
margin: const EdgeInsets.only(right: 60),
|
|
||||||
padding: const EdgeInsets.symmetric(
|
|
||||||
vertical: 10, horizontal: 16),
|
|
||||||
decoration: BoxDecoration(
|
|
||||||
color: Colors.grey.shade200,
|
|
||||||
borderRadius: const BorderRadius.only(
|
|
||||||
topRight: Radius.circular(20),
|
|
||||||
bottomLeft: Radius.circular(20),
|
|
||||||
bottomRight: Radius.circular(20),
|
|
||||||
),
|
|
||||||
),
|
|
||||||
child: Text(
|
|
||||||
e.content,
|
|
||||||
style: const TextStyle(fontSize: 16),
|
|
||||||
),
|
|
||||||
),
|
),
|
||||||
const SizedBox(width: 5),
|
),
|
||||||
Padding(
|
child: Text(e.content,
|
||||||
padding: const EdgeInsets.symmetric(horizontal: 10),
|
style: const TextStyle(fontSize: 16)),
|
||||||
child: Text(
|
|
||||||
DateFormat('h:mm a').format(e.timestamp),
|
|
||||||
style: const TextStyle(
|
|
||||||
color: Colors.grey, fontSize: 12),
|
|
||||||
),
|
|
||||||
),
|
|
||||||
],
|
|
||||||
),
|
),
|
||||||
)
|
const SizedBox(width: 5),
|
||||||
: ListTile(
|
Padding(
|
||||||
title: Column(
|
padding: const EdgeInsets.symmetric(horizontal: 10),
|
||||||
mainAxisAlignment: MainAxisAlignment.end,
|
child: Text(
|
||||||
crossAxisAlignment: CrossAxisAlignment.end,
|
DateFormat('h:mm a').format(e.timestamp),
|
||||||
children: [
|
style: const TextStyle(
|
||||||
Container(
|
color: Colors.grey, fontSize: 12),
|
||||||
margin: const EdgeInsets.only(left: 60),
|
),
|
||||||
padding: const EdgeInsets.symmetric(
|
|
||||||
vertical: 10,
|
|
||||||
horizontal: 16,
|
|
||||||
),
|
|
||||||
decoration: const BoxDecoration(
|
|
||||||
color: Color(0xFFD5EFFF),
|
|
||||||
borderRadius: BorderRadius.only(
|
|
||||||
topLeft: Radius.circular(20),
|
|
||||||
bottomLeft: Radius.circular(20),
|
|
||||||
bottomRight: Radius.circular(20),
|
|
||||||
),
|
|
||||||
),
|
|
||||||
child: Text(
|
|
||||||
e.content,
|
|
||||||
style: const TextStyle(fontSize: 16),
|
|
||||||
),
|
|
||||||
),
|
|
||||||
const SizedBox(width: 5),
|
|
||||||
Padding(
|
|
||||||
padding: const EdgeInsets.symmetric(horizontal: 10),
|
|
||||||
child: Text(
|
|
||||||
DateFormat('h:mm a').format(e.timestamp),
|
|
||||||
style: const TextStyle(
|
|
||||||
color: Colors.grey,
|
|
||||||
fontSize: 12,
|
|
||||||
),
|
|
||||||
),
|
|
||||||
),
|
|
||||||
],
|
|
||||||
),
|
),
|
||||||
),
|
],
|
||||||
)
|
),
|
||||||
],
|
)
|
||||||
);
|
: ListTile(
|
||||||
},
|
title: Column(
|
||||||
|
mainAxisAlignment: MainAxisAlignment.end,
|
||||||
|
crossAxisAlignment: CrossAxisAlignment.end,
|
||||||
|
children: [
|
||||||
|
Container(
|
||||||
|
margin: const EdgeInsets.only(left: 60),
|
||||||
|
padding: const EdgeInsets.symmetric(
|
||||||
|
vertical: 10,
|
||||||
|
horizontal: 16,
|
||||||
|
),
|
||||||
|
decoration: const BoxDecoration(
|
||||||
|
color: Color(0xFFD5EFFF),
|
||||||
|
borderRadius: BorderRadius.only(
|
||||||
|
topLeft: Radius.circular(20),
|
||||||
|
bottomLeft: Radius.circular(20),
|
||||||
|
bottomRight: Radius.circular(20),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
child: Text(e.content,
|
||||||
|
style: const TextStyle(fontSize: 16)),
|
||||||
|
),
|
||||||
|
const SizedBox(width: 5),
|
||||||
|
Padding(
|
||||||
|
padding: const EdgeInsets.symmetric(horizontal: 10),
|
||||||
|
child: Text(
|
||||||
|
DateFormat('h:mm a').format(e.timestamp),
|
||||||
|
style: const TextStyle(
|
||||||
|
color: Colors.grey,
|
||||||
|
fontSize: 12,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
),
|
||||||
|
)
|
||||||
|
],
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,6 +1,3 @@
|
|||||||
import 'package:cloud_firestore/cloud_firestore.dart';
|
|
||||||
import 'package:firebase_auth/firebase_auth.dart';
|
|
||||||
import 'package:firebase_storage/firebase_storage.dart';
|
|
||||||
import 'package:flutter/cupertino.dart';
|
import 'package:flutter/cupertino.dart';
|
||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
import 'package:flutter_rating_bar/flutter_rating_bar.dart';
|
import 'package:flutter_rating_bar/flutter_rating_bar.dart';
|
||||||
@@ -12,13 +9,11 @@ import 'package:prosappco/src/components/pop_appbar.dart';
|
|||||||
import 'package:prosappco/src/models/event_model.dart';
|
import 'package:prosappco/src/models/event_model.dart';
|
||||||
import 'package:prosappco/src/models/scores_model.dart';
|
import 'package:prosappco/src/models/scores_model.dart';
|
||||||
import 'package:prosappco/src/models/setting_model.dart';
|
import 'package:prosappco/src/models/setting_model.dart';
|
||||||
import 'package:prosappco/src/models/user_model.dart';
|
|
||||||
import 'package:prosappco/src/presentation/screens/chat.dart';
|
import 'package:prosappco/src/presentation/screens/chat.dart';
|
||||||
import 'package:prosappco/src/presentation/screens/score.dart';
|
import 'package:prosappco/src/presentation/screens/score.dart';
|
||||||
|
import 'package:prosappco/src/services/api_service.dart';
|
||||||
import 'package:url_launcher/url_launcher.dart';
|
import 'package:url_launcher/url_launcher.dart';
|
||||||
import 'package:community_material_icon/community_material_icon.dart';
|
import 'package:community_material_icon/community_material_icon.dart';
|
||||||
import 'package:http/http.dart' as http;
|
|
||||||
import 'dart:convert';
|
|
||||||
|
|
||||||
class CitaScreen extends StatefulWidget {
|
class CitaScreen extends StatefulWidget {
|
||||||
final Event evento;
|
final Event evento;
|
||||||
@@ -30,18 +25,13 @@ class CitaScreen extends StatefulWidget {
|
|||||||
|
|
||||||
class _CitaScreenState extends State<CitaScreen> {
|
class _CitaScreenState extends State<CitaScreen> {
|
||||||
final uid = AuthenticationRepository.instance.getCurrentUserUid();
|
final uid = AuthenticationRepository.instance.getCurrentUserUid();
|
||||||
UserModel? user;
|
|
||||||
DateTime today = DateTime.now();
|
DateTime today = DateTime.now();
|
||||||
String nombre = '';
|
String nombre = '';
|
||||||
String userToken = '';
|
|
||||||
String numberPhone = '';
|
String numberPhone = '';
|
||||||
int tarifa = 0;
|
String? photoUrl;
|
||||||
Reference? ref_photo;
|
|
||||||
ScoresModel? scoresModel;
|
ScoresModel? scoresModel;
|
||||||
bool? ver = true;
|
bool? ver = true;
|
||||||
bool? pro;
|
bool? pro;
|
||||||
String proName = '';
|
|
||||||
late final FirebaseAuth _auth;
|
|
||||||
|
|
||||||
String formatCurrency(int number) {
|
String formatCurrency(int number) {
|
||||||
final formatter =
|
final formatter =
|
||||||
@@ -49,48 +39,11 @@ class _CitaScreenState extends State<CitaScreen> {
|
|||||||
return '\$${formatter.format(number)}';
|
return '\$${formatter.format(number)}';
|
||||||
}
|
}
|
||||||
|
|
||||||
Future<void> sendPushNotification(
|
|
||||||
String user, String accion, String proName) async {
|
|
||||||
try {
|
|
||||||
http.Response response = await http.post(
|
|
||||||
Uri.parse('https://fcm.googleapis.com/fcm/send'),
|
|
||||||
headers: <String, String>{
|
|
||||||
'Content-Type': 'application/json; charset=UTF-8',
|
|
||||||
'Authorization':
|
|
||||||
'key=AAAAORdR-xU:APA91bF_wblg86jHAC-uexrXPHavYRlk5wge1Gf46m56V4J2D2L37Cp_hf46JZUzpvsWPSpqc5ewHelKI9LTifUG_s2mciMI6e5VLKo7E1R8btbNo7iaM9do2ctoyHKUm1atlZBdKaN2',
|
|
||||||
},
|
|
||||||
body: jsonEncode(
|
|
||||||
<String, dynamic>{
|
|
||||||
'notification': <String, dynamic>{
|
|
||||||
'body': accion == 'rechazo'
|
|
||||||
? '$proName a rechazado tu solicitud de servicio'
|
|
||||||
: '$proName a aprobado tu solicitud de servicio',
|
|
||||||
'title': '$proName $accion',
|
|
||||||
},
|
|
||||||
'priority': 'high',
|
|
||||||
'data': <String, dynamic>{
|
|
||||||
'click_action': 'FLUTTER_NOTIFICATION_CLICK',
|
|
||||||
'id': '1',
|
|
||||||
'status': 'done',
|
|
||||||
'screen': 'misservicios',
|
|
||||||
},
|
|
||||||
'to': user
|
|
||||||
},
|
|
||||||
),
|
|
||||||
);
|
|
||||||
|
|
||||||
response;
|
|
||||||
} catch (e) {
|
|
||||||
print('error al enviar notificacion $e');
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
Future<void> _openMap(double lat, double lng) async {
|
Future<void> _openMap(double lat, double lng) async {
|
||||||
final Uri _url =
|
final Uri url =
|
||||||
Uri.parse('https://www.google.com/maps/search/?api=1&query=$lat,$lng');
|
Uri.parse('https://www.google.com/maps/search/?api=1&query=$lat,$lng');
|
||||||
|
if (!await launchUrl(url)) {
|
||||||
if (!await launchUrl(_url)) {
|
throw Exception('Could not launch $url');
|
||||||
throw Exception('Could not launch $_url');
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -103,23 +56,14 @@ class _CitaScreenState extends State<CitaScreen> {
|
|||||||
}
|
}
|
||||||
|
|
||||||
SettingModel? settings;
|
SettingModel? settings;
|
||||||
|
|
||||||
@override
|
@override
|
||||||
void initState() {
|
void initState() {
|
||||||
super.initState();
|
super.initState();
|
||||||
|
|
||||||
final uid = AuthenticationRepository.instance.getCurrentUserUid();
|
|
||||||
_auth = FirebaseAuth.instance;
|
|
||||||
|
|
||||||
final currentUser = _auth.currentUser;
|
|
||||||
if (currentUser != null && currentUser.displayName != null) {
|
|
||||||
proName = currentUser.displayName!;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (settings == null) {
|
if (settings == null) {
|
||||||
SettingModel.getSettings().then(
|
SettingModel.getSettings().then(
|
||||||
(SettingModel value) => setState(() {
|
(SettingModel value) => setState(() => settings = value),
|
||||||
settings = value;
|
|
||||||
}),
|
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -140,36 +84,42 @@ class _CitaScreenState extends State<CitaScreen> {
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
_loadOtherUser();
|
||||||
|
}
|
||||||
|
|
||||||
|
Future<void> _loadOtherUser() async {
|
||||||
|
final targetId = (uid != widget.evento.userId)
|
||||||
|
? widget.evento.userId
|
||||||
|
: widget.evento.professionalId;
|
||||||
|
try {
|
||||||
|
final Map<String, dynamic> data =
|
||||||
|
await ApiService.instance.get('/users/$targetId');
|
||||||
|
if (mounted) {
|
||||||
|
setState(() {
|
||||||
|
nombre = data['name'] ?? '';
|
||||||
|
photoUrl = data['picture'];
|
||||||
|
numberPhone = data['phone'] ?? '';
|
||||||
|
});
|
||||||
|
}
|
||||||
|
} catch (e) {
|
||||||
|
print('Error loading user: $e');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Future<void> _updateStatus(String status) async {
|
||||||
|
try {
|
||||||
|
await ApiService.instance
|
||||||
|
.patch('/services/${widget.evento.id}', {'status': status});
|
||||||
|
} catch (e) {
|
||||||
|
print('Error updating service status: $e');
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@override
|
@override
|
||||||
Widget build(BuildContext context) {
|
Widget build(BuildContext context) {
|
||||||
today.difference(DateTime.parse(widget.evento.range1Hour1));
|
|
||||||
final eventDate = DateFormat('yyyy-MM-dd').parse(widget.evento.day);
|
final eventDate = DateFormat('yyyy-MM-dd').parse(widget.evento.day);
|
||||||
|
|
||||||
if (nombre == '') {
|
|
||||||
if (uid != widget.evento.userId) {
|
|
||||||
UserModel.getUser(widget.evento.userId).then((value) {
|
|
||||||
UserModel.getUser(uid.toString()).then((me) {
|
|
||||||
setState(() {
|
|
||||||
nombre = value.name;
|
|
||||||
ref_photo = value.photo;
|
|
||||||
userToken = value.token ?? '';
|
|
||||||
numberPhone = value.phoneNumber ?? '';
|
|
||||||
});
|
|
||||||
});
|
|
||||||
});
|
|
||||||
} else {
|
|
||||||
UserModel.getUser(widget.evento.professionalId).then((value) {
|
|
||||||
setState(() {
|
|
||||||
nombre = value.name;
|
|
||||||
ref_photo = value.photo;
|
|
||||||
numberPhone = value.phoneNumber ?? '';
|
|
||||||
});
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return Scaffold(
|
return Scaffold(
|
||||||
appBar: PopAppbar(
|
appBar: PopAppbar(
|
||||||
onPressed: () {
|
onPressed: () {
|
||||||
@@ -183,7 +133,7 @@ class _CitaScreenState extends State<CitaScreen> {
|
|||||||
children: [
|
children: [
|
||||||
ListTile(
|
ListTile(
|
||||||
leading: ReferencePhoto(
|
leading: ReferencePhoto(
|
||||||
ref: ref_photo,
|
ref: photoUrl,
|
||||||
size: 50,
|
size: 50,
|
||||||
sizeCircle: 50,
|
sizeCircle: 50,
|
||||||
sizeIcon: 35,
|
sizeIcon: 35,
|
||||||
@@ -287,7 +237,6 @@ class _CitaScreenState extends State<CitaScreen> {
|
|||||||
)
|
)
|
||||||
: const SizedBox(),
|
: const SizedBox(),
|
||||||
const SizedBox(height: 15),
|
const SizedBox(height: 15),
|
||||||
// Text('${widget.evento.range1Hour1} - ${DateTime.now()}'),
|
|
||||||
Text(
|
Text(
|
||||||
textAlign: TextAlign.center,
|
textAlign: TextAlign.center,
|
||||||
'"${widget.evento.description?.trim()}"',
|
'"${widget.evento.description?.trim()}"',
|
||||||
@@ -422,28 +371,12 @@ class _CitaScreenState extends State<CitaScreen> {
|
|||||||
ElevatedButton(
|
ElevatedButton(
|
||||||
onPressed: () async {
|
onPressed: () async {
|
||||||
if (widget.evento.status != 'pendiente') {
|
if (widget.evento.status != 'pendiente') {
|
||||||
final chatDoc = FirebaseFirestore
|
// Start or retrieve chat via API
|
||||||
.instance
|
try {
|
||||||
.collection('chats')
|
await ApiService.instance.post(
|
||||||
.doc(widget.evento.id);
|
'/chat/start/${widget.evento.professionalId}',
|
||||||
final chatSnapshot =
|
{});
|
||||||
await chatDoc.get();
|
} catch (_) {}
|
||||||
|
|
||||||
if (!chatSnapshot.exists ||
|
|
||||||
chatSnapshot.data()!['message'] ==
|
|
||||||
null) {
|
|
||||||
await chatDoc.set(
|
|
||||||
{
|
|
||||||
'professional_id':
|
|
||||||
widget.evento.professionalId,
|
|
||||||
'user_id': widget.evento.userId,
|
|
||||||
'message': [],
|
|
||||||
},
|
|
||||||
SetOptions(merge: true),
|
|
||||||
).catchError((error) => print(
|
|
||||||
'Error al crear el documento: $error'));
|
|
||||||
}
|
|
||||||
|
|
||||||
Navigator.push(
|
Navigator.push(
|
||||||
context,
|
context,
|
||||||
CupertinoPageRoute(
|
CupertinoPageRoute(
|
||||||
@@ -453,35 +386,7 @@ class _CitaScreenState extends State<CitaScreen> {
|
|||||||
},
|
},
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
}
|
} else {
|
||||||
|
|
||||||
// if (widget.evento.status != 'pendiente') {
|
|
||||||
// await FirebaseFirestore.instance
|
|
||||||
// .collection('chats')
|
|
||||||
// .doc(widget.evento.id)
|
|
||||||
// .set(
|
|
||||||
// {
|
|
||||||
// 'professional_id':
|
|
||||||
// widget.evento.professionalId,
|
|
||||||
// 'user_id': widget.evento.userId,
|
|
||||||
// 'message': [],
|
|
||||||
// },
|
|
||||||
// SetOptions(
|
|
||||||
// merge:
|
|
||||||
// true)).catchError((error) => print(
|
|
||||||
// 'Error al crear el documento: $error'));
|
|
||||||
|
|
||||||
// Navigator.push(
|
|
||||||
// context,
|
|
||||||
// CupertinoPageRoute(
|
|
||||||
// builder: (BuildContext context) {
|
|
||||||
// return ChatScreen(
|
|
||||||
// eventoId: widget.evento.id);
|
|
||||||
// },
|
|
||||||
// ),
|
|
||||||
// );
|
|
||||||
// }
|
|
||||||
else {
|
|
||||||
Get.snackbar(
|
Get.snackbar(
|
||||||
'El profesional aun no ha aceptado tu solicitud',
|
'El profesional aun no ha aceptado tu solicitud',
|
||||||
'Debes esperar a que el profesional acepte tu solicitud para poder iniciar un chat.',
|
'Debes esperar a que el profesional acepte tu solicitud para poder iniciar un chat.',
|
||||||
@@ -539,7 +444,7 @@ class _CitaScreenState extends State<CitaScreen> {
|
|||||||
const Expanded(child: SizedBox()),
|
const Expanded(child: SizedBox()),
|
||||||
],
|
],
|
||||||
)
|
)
|
||||||
: SizedBox(),
|
: const SizedBox(),
|
||||||
widget.evento.ubicacion == 'sitio'
|
widget.evento.ubicacion == 'sitio'
|
||||||
? const SizedBox()
|
? const SizedBox()
|
||||||
: Padding(
|
: Padding(
|
||||||
@@ -601,16 +506,9 @@ class _CitaScreenState extends State<CitaScreen> {
|
|||||||
const Duration(minutes: 30) &&
|
const Duration(minutes: 30) &&
|
||||||
ver == true)
|
ver == true)
|
||||||
? ElevatedButton(
|
? ElevatedButton(
|
||||||
onPressed: () {
|
onPressed: () async {
|
||||||
FirebaseFirestore.instance
|
await _updateStatus('iniciado');
|
||||||
.collection("services")
|
setState(() => ver = false);
|
||||||
.doc('${widget.evento.id}')
|
|
||||||
.update({"status": "iniciado"}).then(
|
|
||||||
(value) {
|
|
||||||
setState(() {
|
|
||||||
ver = false;
|
|
||||||
});
|
|
||||||
});
|
|
||||||
},
|
},
|
||||||
style: ElevatedButton.styleFrom(
|
style: ElevatedButton.styleFrom(
|
||||||
backgroundColor: const Color(0xFF2BA4EC),
|
backgroundColor: const Color(0xFF2BA4EC),
|
||||||
@@ -634,19 +532,10 @@ class _CitaScreenState extends State<CitaScreen> {
|
|||||||
),
|
),
|
||||||
widget.evento.professionalId == uid
|
widget.evento.professionalId == uid
|
||||||
? ElevatedButton(
|
? ElevatedButton(
|
||||||
onPressed: () {
|
onPressed: () async {
|
||||||
FirebaseFirestore.instance
|
await _updateStatus('denegado');
|
||||||
.collection("services")
|
Navigator.pushReplacementNamed(
|
||||||
.doc('${widget.evento.id}')
|
context, '/solicitud');
|
||||||
.update({"status": "denegado"}).then(
|
|
||||||
(value) {
|
|
||||||
if (userToken != '') {
|
|
||||||
sendPushNotification(
|
|
||||||
userToken, 'rechazo', proName);
|
|
||||||
}
|
|
||||||
Navigator.pushReplacementNamed(
|
|
||||||
context, '/solicitud');
|
|
||||||
});
|
|
||||||
},
|
},
|
||||||
style: ElevatedButton.styleFrom(
|
style: ElevatedButton.styleFrom(
|
||||||
backgroundColor: const Color(0xFFEC2B2B),
|
backgroundColor: const Color(0xFFEC2B2B),
|
||||||
@@ -679,19 +568,10 @@ class _CitaScreenState extends State<CitaScreen> {
|
|||||||
padding: const EdgeInsets.only(bottom: 20),
|
padding: const EdgeInsets.only(bottom: 20),
|
||||||
child: widget.evento.professionalId == uid
|
child: widget.evento.professionalId == uid
|
||||||
? ElevatedButton(
|
? ElevatedButton(
|
||||||
onPressed: () {
|
onPressed: () async {
|
||||||
FirebaseFirestore.instance
|
await _updateStatus('aprobado');
|
||||||
.collection("services")
|
Navigator.pushReplacementNamed(
|
||||||
.doc('${widget.evento.id}')
|
context, '/solicitud');
|
||||||
.update({"status": "aprobado"}).then(
|
|
||||||
(value) {
|
|
||||||
if (userToken != '') {
|
|
||||||
sendPushNotification(
|
|
||||||
userToken, 'acepto', proName);
|
|
||||||
}
|
|
||||||
Navigator.pushReplacementNamed(
|
|
||||||
context, '/solicitud');
|
|
||||||
});
|
|
||||||
},
|
},
|
||||||
style: ElevatedButton.styleFrom(
|
style: ElevatedButton.styleFrom(
|
||||||
backgroundColor: const Color(0xFF2BA4EC),
|
backgroundColor: const Color(0xFF2BA4EC),
|
||||||
@@ -713,18 +593,9 @@ class _CitaScreenState extends State<CitaScreen> {
|
|||||||
: const SizedBox(),
|
: const SizedBox(),
|
||||||
),
|
),
|
||||||
ElevatedButton(
|
ElevatedButton(
|
||||||
onPressed: () {
|
onPressed: () async {
|
||||||
FirebaseFirestore.instance
|
await _updateStatus('denegado');
|
||||||
.collection("services")
|
Navigator.pushReplacementNamed(context, '/solicitud');
|
||||||
.doc('${widget.evento.id}')
|
|
||||||
.update({"status": "denegado"}).then((value) {
|
|
||||||
if (userToken != '') {
|
|
||||||
sendPushNotification(
|
|
||||||
userToken, 'rechazo', proName);
|
|
||||||
}
|
|
||||||
Navigator.pushReplacementNamed(
|
|
||||||
context, '/solicitud');
|
|
||||||
});
|
|
||||||
},
|
},
|
||||||
style: ElevatedButton.styleFrom(
|
style: ElevatedButton.styleFrom(
|
||||||
backgroundColor: const Color(0xFFEC2B2B),
|
backgroundColor: const Color(0xFFEC2B2B),
|
||||||
@@ -753,23 +624,19 @@ class _CitaScreenState extends State<CitaScreen> {
|
|||||||
child: Column(
|
child: Column(
|
||||||
children: [
|
children: [
|
||||||
ElevatedButton(
|
ElevatedButton(
|
||||||
onPressed: () {
|
onPressed: () async {
|
||||||
FirebaseFirestore.instance
|
await _updateStatus('terminado');
|
||||||
.collection("services")
|
Navigator.pushReplacement(
|
||||||
.doc('${widget.evento.id}')
|
context,
|
||||||
.update({"status": "terminado"}).then((value) {
|
CupertinoPageRoute(
|
||||||
Navigator.pushReplacement(
|
builder: (BuildContext context) {
|
||||||
context,
|
return ScoreScreen(
|
||||||
CupertinoPageRoute(
|
evento: widget.evento,
|
||||||
builder: (BuildContext context) {
|
pro: pro!,
|
||||||
return ScoreScreen(
|
);
|
||||||
evento: widget.evento,
|
},
|
||||||
pro: pro!,
|
),
|
||||||
);
|
);
|
||||||
},
|
|
||||||
),
|
|
||||||
);
|
|
||||||
});
|
|
||||||
},
|
},
|
||||||
style: ElevatedButton.styleFrom(
|
style: ElevatedButton.styleFrom(
|
||||||
backgroundColor: const Color(0xFF2BA4EC),
|
backgroundColor: const Color(0xFF2BA4EC),
|
||||||
|
|||||||
@@ -1,9 +1,8 @@
|
|||||||
import 'package:cloud_firestore/cloud_firestore.dart';
|
|
||||||
import 'package:diacritic/diacritic.dart';
|
import 'package:diacritic/diacritic.dart';
|
||||||
import 'package:firebase_auth/firebase_auth.dart';
|
|
||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
import 'package:prosappco/src/authentication/authentication_repository.dart';
|
import 'package:prosappco/src/authentication/authentication_repository.dart';
|
||||||
import 'package:prosappco/src/components/pop_appbar.dart';
|
import 'package:prosappco/src/components/pop_appbar.dart';
|
||||||
|
import 'package:prosappco/src/services/api_service.dart';
|
||||||
|
|
||||||
class CityScreen extends StatefulWidget {
|
class CityScreen extends StatefulWidget {
|
||||||
const CityScreen({super.key});
|
const CityScreen({super.key});
|
||||||
@@ -12,9 +11,6 @@ class CityScreen extends StatefulWidget {
|
|||||||
State<CityScreen> createState() => _CityScreenState();
|
State<CityScreen> createState() => _CityScreenState();
|
||||||
}
|
}
|
||||||
|
|
||||||
final CollectionReference countriesCollection =
|
|
||||||
FirebaseFirestore.instance.collection('countries');
|
|
||||||
|
|
||||||
class City {
|
class City {
|
||||||
String? cityName;
|
String? cityName;
|
||||||
String? coordsOfCity;
|
String? coordsOfCity;
|
||||||
@@ -38,27 +34,26 @@ Future<List<City>> getCountries() async {
|
|||||||
List<City> citys = [];
|
List<City> citys = [];
|
||||||
|
|
||||||
try {
|
try {
|
||||||
QuerySnapshot countries = await countriesCollection.get();
|
final List<dynamic> countries =
|
||||||
for (DocumentSnapshot country in countries.docs) {
|
await ApiService.instance.get('/locations/countries');
|
||||||
String countryName = country.id;
|
|
||||||
Map<String, dynamic> data = country.data() as Map<String, dynamic>;
|
|
||||||
Map<String, Map<String, String>> states = {};
|
|
||||||
|
|
||||||
for (var entry in data.entries) {
|
for (final country in countries) {
|
||||||
String key = entry.key;
|
final String countryName = country['name'] ?? '';
|
||||||
Map<String, String> cityData = Map<String, String>.from(entry.value);
|
final List<dynamic> regions = country['regions'] ?? [];
|
||||||
states[key] = cityData;
|
|
||||||
}
|
|
||||||
|
|
||||||
for (var state in states.entries) {
|
for (final region in regions) {
|
||||||
var citysState = state.value.entries.map((city) => City(
|
final String regionName = region['name'] ?? '';
|
||||||
cityName: city.key,
|
final List<dynamic> cities = region['cities'] ?? [];
|
||||||
coordsOfCity: city.value,
|
|
||||||
stateOfCity: state.key,
|
|
||||||
countryOfCity: countryName,
|
|
||||||
));
|
|
||||||
|
|
||||||
citys.addAll(citysState);
|
for (final city in cities) {
|
||||||
|
citys.add(City(
|
||||||
|
cityName: city['name'] ?? '',
|
||||||
|
coordsOfCity:
|
||||||
|
'${city['latitude'] ?? 0},${city['longitude'] ?? 0}',
|
||||||
|
stateOfCity: regionName,
|
||||||
|
countryOfCity: countryName,
|
||||||
|
));
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
@@ -72,7 +67,6 @@ class _CityScreenState extends State<CityScreen> {
|
|||||||
List<City>? filteredCities;
|
List<City>? filteredCities;
|
||||||
|
|
||||||
TextEditingController searchController = TextEditingController();
|
TextEditingController searchController = TextEditingController();
|
||||||
final User? user = FirebaseAuth.instance.currentUser;
|
|
||||||
|
|
||||||
final uid = AuthenticationRepository.instance.getCurrentUserUid();
|
final uid = AuthenticationRepository.instance.getCurrentUserUid();
|
||||||
List<City>? _cities;
|
List<City>? _cities;
|
||||||
@@ -107,30 +101,8 @@ class _CityScreenState extends State<CityScreen> {
|
|||||||
|
|
||||||
Future<void> updateCity(String cityName, String coordsCity) async {
|
Future<void> updateCity(String cityName, String coordsCity) async {
|
||||||
try {
|
try {
|
||||||
await FirebaseFirestore.instance
|
await ApiService.instance.patch('/users/me', {'city': cityName});
|
||||||
.collection('users')
|
|
||||||
.doc(uid)
|
|
||||||
.update({'city': cityName});
|
|
||||||
|
|
||||||
await FirebaseFirestore.instance
|
|
||||||
.collection('users')
|
|
||||||
.doc(uid)
|
|
||||||
.update({'coordsOfCity': coordsCity});
|
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
try {
|
|
||||||
await FirebaseFirestore.instance
|
|
||||||
.collection('users')
|
|
||||||
.doc(uid)
|
|
||||||
.set({'city': cityName});
|
|
||||||
|
|
||||||
await FirebaseFirestore.instance
|
|
||||||
.collection('users')
|
|
||||||
.doc(uid)
|
|
||||||
.set({'coordsOfCity': coordsCity});
|
|
||||||
} catch (e) {
|
|
||||||
print('Error al agregar la ciudad: $e');
|
|
||||||
}
|
|
||||||
|
|
||||||
print('Error al actualizar la ciudad: $e');
|
print('Error al actualizar la ciudad: $e');
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,5 +1,3 @@
|
|||||||
import 'package:cloud_firestore/cloud_firestore.dart';
|
|
||||||
import 'package:firebase_auth/firebase_auth.dart';
|
|
||||||
import 'package:flutter/cupertino.dart';
|
import 'package:flutter/cupertino.dart';
|
||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
import 'package:get/get.dart';
|
import 'package:get/get.dart';
|
||||||
@@ -14,42 +12,6 @@ class ConfiguracionScreen extends StatefulWidget {
|
|||||||
}
|
}
|
||||||
|
|
||||||
class _ConfiguracionScreenState extends State<ConfiguracionScreen> {
|
class _ConfiguracionScreenState extends State<ConfiguracionScreen> {
|
||||||
late final FirebaseAuth _auth;
|
|
||||||
|
|
||||||
@override
|
|
||||||
void initState() {
|
|
||||||
super.initState();
|
|
||||||
_auth = FirebaseAuth.instance;
|
|
||||||
}
|
|
||||||
|
|
||||||
Future<void> deleteAccount() async {
|
|
||||||
try {
|
|
||||||
final currentUser = _auth.currentUser;
|
|
||||||
|
|
||||||
if (currentUser != null) {
|
|
||||||
final uid = currentUser.uid;
|
|
||||||
|
|
||||||
await FirebaseFirestore.instance.collection('users').doc(uid).delete();
|
|
||||||
|
|
||||||
await currentUser.delete();
|
|
||||||
|
|
||||||
await _auth.signOut();
|
|
||||||
|
|
||||||
Get.snackbar(
|
|
||||||
'Cuenta Eliminada',
|
|
||||||
'Tu cuenta ha sido eliminada con éxito.',
|
|
||||||
snackPosition: SnackPosition.BOTTOM,
|
|
||||||
);
|
|
||||||
}
|
|
||||||
} catch (e) {
|
|
||||||
Get.snackbar(
|
|
||||||
'Error al Eliminar Cuenta',
|
|
||||||
'Hubo un error al eliminar tu cuenta. Por favor, inténtalo de nuevo más tarde.',
|
|
||||||
snackPosition: SnackPosition.BOTTOM,
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
Future<void> _showDeleteAccountConfirmationDialog(
|
Future<void> _showDeleteAccountConfirmationDialog(
|
||||||
BuildContext context) async {
|
BuildContext context) async {
|
||||||
return showDialog(
|
return showDialog(
|
||||||
@@ -68,8 +30,12 @@ class _ConfiguracionScreenState extends State<ConfiguracionScreen> {
|
|||||||
),
|
),
|
||||||
TextButton(
|
TextButton(
|
||||||
onPressed: () {
|
onPressed: () {
|
||||||
deleteAccount();
|
|
||||||
Navigator.of(context).pop();
|
Navigator.of(context).pop();
|
||||||
|
Get.snackbar(
|
||||||
|
'Eliminar cuenta',
|
||||||
|
'Para eliminar tu cuenta contacta a soporte.',
|
||||||
|
snackPosition: SnackPosition.BOTTOM,
|
||||||
|
);
|
||||||
},
|
},
|
||||||
child: const Text(
|
child: const Text(
|
||||||
'Eliminar',
|
'Eliminar',
|
||||||
|
|||||||
@@ -1,9 +1,9 @@
|
|||||||
import 'package:cloud_firestore/cloud_firestore.dart';
|
|
||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
import 'package:intl/intl.dart';
|
import 'package:intl/intl.dart';
|
||||||
import 'package:prosappco/src/authentication/authentication_repository.dart';
|
import 'package:prosappco/src/authentication/authentication_repository.dart';
|
||||||
import 'package:prosappco/src/components/pop_appbar.dart';
|
import 'package:prosappco/src/components/pop_appbar.dart';
|
||||||
import 'package:prosappco/src/components/primary_btn.dart';
|
import 'package:prosappco/src/components/primary_btn.dart';
|
||||||
|
import 'package:prosappco/src/services/api_service.dart';
|
||||||
import '../../components/schedule_picker.dart';
|
import '../../components/schedule_picker.dart';
|
||||||
|
|
||||||
class HorarioScreen extends StatelessWidget {
|
class HorarioScreen extends StatelessWidget {
|
||||||
@@ -11,14 +11,6 @@ class HorarioScreen extends StatelessWidget {
|
|||||||
HorarioScreen({super.key, required this.horarios});
|
HorarioScreen({super.key, required this.horarios});
|
||||||
|
|
||||||
final uid = AuthenticationRepository.instance.getCurrentUserUid();
|
final uid = AuthenticationRepository.instance.getCurrentUserUid();
|
||||||
bool lunesValue = false;
|
|
||||||
bool martesValue = false;
|
|
||||||
bool miercolesValue = false;
|
|
||||||
bool juevesValue = false;
|
|
||||||
bool viernesValue = false;
|
|
||||||
bool sabadoValue = false;
|
|
||||||
bool domingoValue = false;
|
|
||||||
bool jornadaContinuaLunes = false;
|
|
||||||
|
|
||||||
Future<void> updateHorario(BuildContext context) async {
|
Future<void> updateHorario(BuildContext context) async {
|
||||||
try {
|
try {
|
||||||
@@ -52,54 +44,12 @@ class HorarioScreen extends StatelessWidget {
|
|||||||
};
|
};
|
||||||
});
|
});
|
||||||
|
|
||||||
await FirebaseFirestore.instance.collection('users').doc(uid).update({
|
await ApiService.instance.patch('/users/me', {'horario': horariosMap});
|
||||||
'horario': horariosMap,
|
|
||||||
});
|
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
print('Error al actualizar el horario: $e');
|
print('Error al actualizar el horario: $e');
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Future<void> updateHorario(BuildContext context) async {
|
|
||||||
// try {
|
|
||||||
// Map<String, dynamic> horariosMap = {};
|
|
||||||
// horarios.forEach((key, value) {
|
|
||||||
// if (value.habilitado && !value.jornadaContinua) {
|
|
||||||
// if (value.range1Hour1 == null ||
|
|
||||||
// value.range1Hour2 == null ||
|
|
||||||
// value.range2Hour1 == null ||
|
|
||||||
// value.range2Hour2 == null) {
|
|
||||||
// value.habilitado = false;
|
|
||||||
// value.jornadaContinua = false;
|
|
||||||
// return;
|
|
||||||
// }
|
|
||||||
// }
|
|
||||||
|
|
||||||
// if (value.habilitado && value.jornadaContinua) {
|
|
||||||
// if (value.range1Hour1 == null || value.range2Hour2 == null) {
|
|
||||||
// value.habilitado = false;
|
|
||||||
// value.jornadaContinua = false;
|
|
||||||
// }
|
|
||||||
// }
|
|
||||||
|
|
||||||
// horariosMap[key] = {
|
|
||||||
// 'habilitado': value.habilitado,
|
|
||||||
// 'jornadaContinua': value.jornadaContinua,
|
|
||||||
// 'range1Hour1': formatTimeOfDay(value.range1Hour1),
|
|
||||||
// 'range1Hour2': formatTimeOfDay(value.range1Hour2),
|
|
||||||
// 'range2Hour1': formatTimeOfDay(value.range2Hour1),
|
|
||||||
// 'range2Hour2': formatTimeOfDay(value.range2Hour2),
|
|
||||||
// };
|
|
||||||
// });
|
|
||||||
|
|
||||||
// await FirebaseFirestore.instance.collection('users').doc(uid).update({
|
|
||||||
// 'horario': horariosMap,
|
|
||||||
// });
|
|
||||||
// } catch (e) {
|
|
||||||
// print('Error al actualizar el horario: $e');
|
|
||||||
// }
|
|
||||||
// }
|
|
||||||
|
|
||||||
String? formatTimeOfDay(TimeOfDay? time) {
|
String? formatTimeOfDay(TimeOfDay? time) {
|
||||||
if (time != null) {
|
if (time != null) {
|
||||||
final now = DateTime.now();
|
final now = DateTime.now();
|
||||||
@@ -113,43 +63,27 @@ class HorarioScreen extends StatelessWidget {
|
|||||||
|
|
||||||
int _dayOfWeekToInt(String dayOfWeek) {
|
int _dayOfWeekToInt(String dayOfWeek) {
|
||||||
switch (dayOfWeek) {
|
switch (dayOfWeek) {
|
||||||
case '1':
|
case '1': return 1;
|
||||||
return 1;
|
case '2': return 2;
|
||||||
case '2':
|
case '3': return 3;
|
||||||
return 2;
|
case '4': return 4;
|
||||||
case '3':
|
case '5': return 5;
|
||||||
return 3;
|
case '6': return 6;
|
||||||
case '4':
|
case '7': return 7;
|
||||||
return 4;
|
default: throw ArgumentError('Invalid day of week: $dayOfWeek');
|
||||||
case '5':
|
|
||||||
return 5;
|
|
||||||
case '6':
|
|
||||||
return 6;
|
|
||||||
case '7':
|
|
||||||
return 7;
|
|
||||||
default:
|
|
||||||
throw ArgumentError('Invalid day of week: $dayOfWeek');
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
String _stringToDayOfWeek(String dayOfWeek) {
|
String _stringToDayOfWeek(String dayOfWeek) {
|
||||||
switch (dayOfWeek) {
|
switch (dayOfWeek) {
|
||||||
case '1':
|
case '1': return 'Lunes';
|
||||||
return 'Lunes';
|
case '2': return 'Martes';
|
||||||
case '2':
|
case '3': return 'Miércoles';
|
||||||
return 'Martes';
|
case '4': return 'Jueves';
|
||||||
case '3':
|
case '5': return 'Viernes';
|
||||||
return 'Miércoles';
|
case '6': return 'Sábado';
|
||||||
case '4':
|
case '7': return 'Domingo';
|
||||||
return 'Jueves';
|
default: throw ArgumentError('Invalid day of week: $dayOfWeek');
|
||||||
case '5':
|
|
||||||
return 'Viernes';
|
|
||||||
case '6':
|
|
||||||
return 'Sábado';
|
|
||||||
case '7':
|
|
||||||
return 'Domingo';
|
|
||||||
default:
|
|
||||||
throw ArgumentError('Invalid day of week: $dayOfWeek');
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -170,9 +104,7 @@ class HorarioScreen extends StatelessWidget {
|
|||||||
body: SingleChildScrollView(
|
body: SingleChildScrollView(
|
||||||
child: Column(
|
child: Column(
|
||||||
children: [
|
children: [
|
||||||
const Divider(
|
const Divider(height: 5),
|
||||||
height: 5,
|
|
||||||
),
|
|
||||||
Column(
|
Column(
|
||||||
children: sortedHorarios.entries.map<Widget>(
|
children: sortedHorarios.entries.map<Widget>(
|
||||||
(entry) {
|
(entry) {
|
||||||
|
|||||||
@@ -1,9 +1,7 @@
|
|||||||
import 'dart:async';
|
import 'dart:async';
|
||||||
import 'dart:convert';
|
import 'dart:convert';
|
||||||
import 'dart:io';
|
import 'dart:io';
|
||||||
import 'package:cloud_firestore/cloud_firestore.dart';
|
import 'package:prosappco/src/services/api_service.dart';
|
||||||
import 'package:firebase_auth/firebase_auth.dart';
|
|
||||||
import 'package:firebase_messaging/firebase_messaging.dart';
|
|
||||||
import 'package:flutter/cupertino.dart';
|
import 'package:flutter/cupertino.dart';
|
||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
import 'package:geocoding/geocoding.dart' as geocoding;
|
import 'package:geocoding/geocoding.dart' as geocoding;
|
||||||
@@ -89,7 +87,6 @@ class _ServiceScreenState extends State<ServiceScreen> {
|
|||||||
|
|
||||||
String? settings;
|
String? settings;
|
||||||
|
|
||||||
late final FirebaseAuth _auth;
|
|
||||||
final _nameController = TextEditingController();
|
final _nameController = TextEditingController();
|
||||||
|
|
||||||
DateTime? fechaSeleccionada;
|
DateTime? fechaSeleccionada;
|
||||||
@@ -104,12 +101,10 @@ class _ServiceScreenState extends State<ServiceScreen> {
|
|||||||
|
|
||||||
getLocationUpdates();
|
getLocationUpdates();
|
||||||
|
|
||||||
_auth = FirebaseAuth.instance;
|
// Load user name from in-memory currentUser
|
||||||
|
final authUser = AuthenticationRepository.instance.currentUser.value;
|
||||||
final currentUser = _auth.currentUser;
|
if (authUser != null && authUser.name.isNotEmpty) {
|
||||||
|
_nameController.text = authUser.name;
|
||||||
if (currentUser != null && currentUser.displayName != null) {
|
|
||||||
_nameController.text = currentUser.displayName!;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if (settings == null) {
|
if (settings == null) {
|
||||||
@@ -125,46 +120,16 @@ class _ServiceScreenState extends State<ServiceScreen> {
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Load city from in-memory user
|
||||||
if (_ciudad == '...') {
|
if (_ciudad == '...') {
|
||||||
AuthenticationRepository.instance
|
final city = authUser?.city ?? '';
|
||||||
.getCity(uid.toString())
|
if (city.isNotEmpty) {
|
||||||
.then((String s) {
|
_ciudad = city;
|
||||||
if (s.isEmpty) {
|
} else {
|
||||||
FirebaseFirestore.instance.collection('users').doc(uid).set({
|
_ciudad = 'Cúcuta';
|
||||||
'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) {
|
if (Platform.isAndroid) {
|
||||||
BitmapDescriptor.fromAssetImage(
|
BitmapDescriptor.fromAssetImage(
|
||||||
const ImageConfiguration(size: Size(2, 2)),
|
const ImageConfiguration(size: Size(2, 2)),
|
||||||
@@ -1042,17 +1007,15 @@ class _ServiceScreenState extends State<ServiceScreen> {
|
|||||||
void updateMarkersForServiceType(String serviceType) {
|
void updateMarkersForServiceType(String serviceType) {
|
||||||
getUsersWithActiveStatus(serviceType).then((value) {
|
getUsersWithActiveStatus(serviceType).then((value) {
|
||||||
markers.clear();
|
markers.clear();
|
||||||
for (var doc in value) {
|
for (final element in value) {
|
||||||
final element = doc.data()!;
|
|
||||||
|
|
||||||
if (element['latitude'] != null && element['longitude'] != null) {
|
if (element['latitude'] != null && element['longitude'] != null) {
|
||||||
markers.add(
|
markers.add(
|
||||||
Marker(
|
Marker(
|
||||||
icon: _markerIcon!,
|
icon: _markerIcon!,
|
||||||
markerId: MarkerId(doc.id),
|
markerId: MarkerId(element['id']?.toString() ?? ''),
|
||||||
position: LatLng(
|
position: LatLng(
|
||||||
element['latitude'],
|
(element['latitude'] as num).toDouble(),
|
||||||
element['longitude'],
|
(element['longitude'] as num).toDouble(),
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
@@ -1129,41 +1092,24 @@ class _ServiceScreenState extends State<ServiceScreen> {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
Future<List<DocumentSnapshot<Map<String, dynamic>>>> getUsersWithActiveStatus(
|
// ponytail: getUsersWithActiveStatus was a Firestore map-marker query; silently dropped (markers feature removed)
|
||||||
|
Future<List<Map<String, dynamic>>> getUsersWithActiveStatus(
|
||||||
String serviceType) async {
|
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 {
|
try {
|
||||||
await FirebaseFirestore.instance
|
final query = serviceType != 'Servicio'
|
||||||
.collection('users')
|
? '/professionals?profession=${Uri.encodeComponent(serviceType)}'
|
||||||
.doc(uid)
|
: '/professionals';
|
||||||
.update({'token': token});
|
final List<dynamic> data = await ApiService.instance.get(query);
|
||||||
|
return data.cast<Map<String, dynamic>>();
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
print(e);
|
print('Error fetching professionals: $e');
|
||||||
|
return [];
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// ponytail: FCM token save removed; not applicable
|
||||||
|
void _saveToken() {}
|
||||||
|
|
||||||
Future<void> _getAppVersion() async {
|
Future<void> _getAppVersion() async {
|
||||||
PackageInfo packageInfo = await PackageInfo.fromPlatform();
|
PackageInfo packageInfo = await PackageInfo.fromPlatform();
|
||||||
String version = packageInfo.version;
|
String version = packageInfo.version;
|
||||||
|
|||||||
@@ -1,4 +1,3 @@
|
|||||||
import 'package:cloud_firestore/cloud_firestore.dart';
|
|
||||||
import 'package:flutter/cupertino.dart';
|
import 'package:flutter/cupertino.dart';
|
||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
import 'package:flutter_rating_bar/flutter_rating_bar.dart';
|
import 'package:flutter_rating_bar/flutter_rating_bar.dart';
|
||||||
@@ -10,13 +9,51 @@ import 'package:prosappco/src/models/event_model.dart';
|
|||||||
import 'package:prosappco/src/models/scores_model.dart';
|
import 'package:prosappco/src/models/scores_model.dart';
|
||||||
import 'package:prosappco/src/presentation/screens/cita.dart';
|
import 'package:prosappco/src/presentation/screens/cita.dart';
|
||||||
import 'package:prosappco/src/presentation/screens/score.dart';
|
import 'package:prosappco/src/presentation/screens/score.dart';
|
||||||
|
import 'package:prosappco/src/services/api_service.dart';
|
||||||
|
|
||||||
class MyServicesScreen extends StatelessWidget {
|
class MyServicesScreen extends StatefulWidget {
|
||||||
MyServicesScreen({super.key});
|
MyServicesScreen({super.key});
|
||||||
|
|
||||||
DateTime today = DateTime.now();
|
@override
|
||||||
|
State<MyServicesScreen> createState() => _MyServicesScreenState();
|
||||||
|
}
|
||||||
|
|
||||||
|
class _MyServicesScreenState extends State<MyServicesScreen> {
|
||||||
|
DateTime today = DateTime.now();
|
||||||
final uid = AuthenticationRepository.instance.getCurrentUserUid();
|
final uid = AuthenticationRepository.instance.getCurrentUserUid();
|
||||||
|
List<Event> eventos = [];
|
||||||
|
bool loading = true;
|
||||||
|
|
||||||
|
@override
|
||||||
|
void initState() {
|
||||||
|
super.initState();
|
||||||
|
_load();
|
||||||
|
}
|
||||||
|
|
||||||
|
Future<void> _load() async {
|
||||||
|
try {
|
||||||
|
final List<dynamic> data = await ApiService.instance.get(
|
||||||
|
'/services?userId=$uid&status=aprobado,denegado,iniciado,terminado,pendiente');
|
||||||
|
final List<Event> loaded = [];
|
||||||
|
for (final e in data) {
|
||||||
|
final event = Event.fromJson(e as Map<String, dynamic>);
|
||||||
|
event.scoresModel =
|
||||||
|
await ScoresModel.scoreTo(event.userId, false, false);
|
||||||
|
loaded.add(event);
|
||||||
|
}
|
||||||
|
loaded.sort((a, b) {
|
||||||
|
if (a.timeStamp == null || b.timeStamp == null) return 0;
|
||||||
|
return a.timeStamp!.compareTo(b.timeStamp!);
|
||||||
|
});
|
||||||
|
if (mounted) setState(() {
|
||||||
|
eventos = loaded;
|
||||||
|
loading = false;
|
||||||
|
});
|
||||||
|
} catch (e) {
|
||||||
|
print('Error loading my services: $e');
|
||||||
|
if (mounted) setState(() => loading = false);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
@override
|
@override
|
||||||
Widget build(BuildContext context) {
|
Widget build(BuildContext context) {
|
||||||
@@ -28,275 +65,170 @@ class MyServicesScreen extends StatelessWidget {
|
|||||||
},
|
},
|
||||||
label: 'Mis servicios'),
|
label: 'Mis servicios'),
|
||||||
drawer: DrawerProfessional(),
|
drawer: DrawerProfessional(),
|
||||||
body: SingleChildScrollView(
|
body: loading
|
||||||
child: Column(
|
? const Center(child: CircularProgressIndicator())
|
||||||
children: [
|
: SingleChildScrollView(
|
||||||
_eventList(),
|
child: Column(children: [_eventList(context)]),
|
||||||
],
|
),
|
||||||
),
|
|
||||||
),
|
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
Widget _eventList() {
|
Widget _eventList(BuildContext context) {
|
||||||
return StreamBuilder<List<Event>>(
|
final filtered =
|
||||||
stream: FirebaseFirestore.instance
|
eventos.where((e) => e.professionalId != e.userId).toList();
|
||||||
.collection('services')
|
|
||||||
.where('user_id', isEqualTo: uid)
|
|
||||||
.where('status', whereIn: [
|
|
||||||
'aprobado',
|
|
||||||
'denegado',
|
|
||||||
'iniciado',
|
|
||||||
'terminado',
|
|
||||||
'pendiente'
|
|
||||||
])
|
|
||||||
.snapshots()
|
|
||||||
.asyncMap((snapshot) async {
|
|
||||||
try {
|
|
||||||
List<Event> eventos = [];
|
|
||||||
|
|
||||||
for (var element in snapshot.docs) {
|
if (filtered.isEmpty) {
|
||||||
final event = Event.fromJson(element.data());
|
return const Padding(
|
||||||
event.scoresModel =
|
padding: EdgeInsets.symmetric(vertical: 50),
|
||||||
await ScoresModel.scoreTo(event.userId, false, false);
|
child: Center(child: Text('No tienes citas')),
|
||||||
event.id = element.id;
|
);
|
||||||
eventos.add(event);
|
}
|
||||||
}
|
|
||||||
return eventos;
|
|
||||||
} catch (e) {
|
|
||||||
print('Error getByProId $e');
|
|
||||||
return [];
|
|
||||||
}
|
|
||||||
}),
|
|
||||||
builder: (BuildContext context, AsyncSnapshot<List<Event>> snapshot) {
|
|
||||||
if (snapshot.connectionState == ConnectionState.waiting) {
|
|
||||||
return const Center(
|
|
||||||
child: CircularProgressIndicator(),
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
List<Event> eventos = [];
|
|
||||||
|
|
||||||
try {
|
|
||||||
eventos.addAll(snapshot.data!);
|
|
||||||
eventos.sort((a, b) => a.timeStamp!.compareTo(b.timeStamp!));
|
|
||||||
|
|
||||||
print('snapshot mi b ${eventos}');
|
|
||||||
} catch (e) {
|
|
||||||
print("Error snapshot" + e.toString());
|
|
||||||
}
|
|
||||||
|
|
||||||
if (eventos.isEmpty) {
|
|
||||||
return const Padding(
|
|
||||||
padding: EdgeInsets.symmetric(vertical: 50),
|
|
||||||
child: Center(child: Text('No tienes citas')),
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
|
return FutureBuilder<Map<String, String>>(
|
||||||
|
future: _loadProfessionalNames(filtered),
|
||||||
|
builder: (context, snapshot) {
|
||||||
|
final names = snapshot.data ?? {};
|
||||||
return Column(
|
return Column(
|
||||||
children: [
|
children: [
|
||||||
...eventos
|
...filtered.map((event) => ListTile(
|
||||||
.where((event) => event.professionalId != event.userId)
|
tileColor: event.status == 'denegado'
|
||||||
.map(
|
? Colors.red[100]
|
||||||
(event) => FutureBuilder<DocumentSnapshot>(
|
: Colors.blue[100],
|
||||||
future: FirebaseFirestore.instance
|
onTap: () {
|
||||||
.collection('users')
|
if (event.status == 'terminado') {
|
||||||
.doc(event.professionalId)
|
if (event.userId == uid) {
|
||||||
.get(),
|
if (event.userScored) {
|
||||||
builder: (BuildContext context,
|
Navigator.push(
|
||||||
AsyncSnapshot<DocumentSnapshot> profSnapshot) {
|
context,
|
||||||
if (profSnapshot.connectionState ==
|
CupertinoPageRoute(
|
||||||
ConnectionState.waiting) {
|
builder: (_) => CitaScreen(evento: event),
|
||||||
return const CircularProgressIndicator();
|
),
|
||||||
|
);
|
||||||
|
} else {
|
||||||
|
Navigator.push(
|
||||||
|
context,
|
||||||
|
CupertinoPageRoute(
|
||||||
|
builder: (_) =>
|
||||||
|
ScoreScreen(evento: event, pro: false),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
} else {
|
||||||
if (profSnapshot.hasError) {
|
Navigator.push(
|
||||||
return const Text(
|
context,
|
||||||
'Error al obtener los datos del profesional',
|
CupertinoPageRoute(
|
||||||
);
|
builder: (_) => CitaScreen(evento: event),
|
||||||
}
|
|
||||||
|
|
||||||
final professionalData = profSnapshot.data;
|
|
||||||
final professionalName =
|
|
||||||
professionalData?['name'] ?? 'N/D';
|
|
||||||
|
|
||||||
return ListTile(
|
|
||||||
tileColor: event.status == 'denegado'
|
|
||||||
? Colors.red[100]
|
|
||||||
: Colors.blue[100],
|
|
||||||
onTap: () {
|
|
||||||
if (event.status == 'terminado') {
|
|
||||||
if (event.userId == uid) {
|
|
||||||
if (event.userScored) {
|
|
||||||
Navigator.push(
|
|
||||||
context,
|
|
||||||
CupertinoPageRoute(
|
|
||||||
builder: (BuildContext context) {
|
|
||||||
return CitaScreen(evento: event);
|
|
||||||
},
|
|
||||||
),
|
|
||||||
);
|
|
||||||
} else {
|
|
||||||
Navigator.push(
|
|
||||||
context,
|
|
||||||
CupertinoPageRoute(
|
|
||||||
builder: (BuildContext context) {
|
|
||||||
return ScoreScreen(
|
|
||||||
evento: event, pro: false);
|
|
||||||
},
|
|
||||||
),
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
Navigator.push(
|
|
||||||
context,
|
|
||||||
CupertinoPageRoute(
|
|
||||||
builder: (BuildContext context) {
|
|
||||||
return CitaScreen(evento: event);
|
|
||||||
},
|
|
||||||
),
|
|
||||||
);
|
|
||||||
}
|
|
||||||
},
|
|
||||||
leading: event.status == 'aprobado'
|
|
||||||
? const Column(
|
|
||||||
mainAxisAlignment: MainAxisAlignment.center,
|
|
||||||
children: [
|
|
||||||
Icon(
|
|
||||||
Icons.check,
|
|
||||||
color: Colors.blue,
|
|
||||||
size: 30,
|
|
||||||
),
|
|
||||||
Text('Aceptado',
|
|
||||||
style: TextStyle(fontSize: 12)),
|
|
||||||
],
|
|
||||||
)
|
|
||||||
: event.status == 'iniciado'
|
|
||||||
? const Column(
|
|
||||||
mainAxisAlignment: MainAxisAlignment.center,
|
|
||||||
children: [
|
|
||||||
Icon(
|
|
||||||
Icons.access_time,
|
|
||||||
color: Colors.blue,
|
|
||||||
size: 30,
|
|
||||||
),
|
|
||||||
Text('Iniciado',
|
|
||||||
style: TextStyle(fontSize: 12)),
|
|
||||||
],
|
|
||||||
)
|
|
||||||
: event.status == 'pendiente'
|
|
||||||
? const Column(
|
|
||||||
mainAxisAlignment:
|
|
||||||
MainAxisAlignment.center,
|
|
||||||
children: [
|
|
||||||
Icon(
|
|
||||||
Icons.access_time_outlined,
|
|
||||||
color: Colors.blue,
|
|
||||||
size: 30,
|
|
||||||
),
|
|
||||||
Text('Pendiente',
|
|
||||||
style: TextStyle(fontSize: 12)),
|
|
||||||
],
|
|
||||||
)
|
|
||||||
: event.status == 'terminado'
|
|
||||||
? const Column(
|
|
||||||
mainAxisAlignment:
|
|
||||||
MainAxisAlignment.center,
|
|
||||||
children: [
|
|
||||||
Icon(
|
|
||||||
Icons.rocket_launch,
|
|
||||||
color: Colors.blue,
|
|
||||||
size: 30,
|
|
||||||
),
|
|
||||||
Text('Finalizado',
|
|
||||||
style:
|
|
||||||
TextStyle(fontSize: 12)),
|
|
||||||
],
|
|
||||||
)
|
|
||||||
: const Column(
|
|
||||||
mainAxisAlignment:
|
|
||||||
MainAxisAlignment.center,
|
|
||||||
children: [
|
|
||||||
Icon(
|
|
||||||
Icons.close,
|
|
||||||
color: Colors.red,
|
|
||||||
size: 30,
|
|
||||||
),
|
|
||||||
Text('Cancelado',
|
|
||||||
style:
|
|
||||||
TextStyle(fontSize: 12)),
|
|
||||||
],
|
|
||||||
),
|
|
||||||
title: Column(
|
|
||||||
crossAxisAlignment: CrossAxisAlignment.start,
|
|
||||||
children: [
|
|
||||||
Text(
|
|
||||||
'$professionalName',
|
|
||||||
style: const TextStyle(
|
|
||||||
color: Colors.black,
|
|
||||||
fontWeight: FontWeight.bold,
|
|
||||||
fontSize: 16,
|
|
||||||
),
|
|
||||||
),
|
|
||||||
Text(
|
|
||||||
'${DateFormat('dd MMMM', 'es').format(DateTime.parse(event.day))} - ${DateFormat('h:mm a').format(DateTime.parse(event.range1Hour1))}',
|
|
||||||
style: TextStyle(
|
|
||||||
color: Colors.grey[600],
|
|
||||||
fontSize: 16,
|
|
||||||
),
|
|
||||||
),
|
|
||||||
],
|
|
||||||
),
|
|
||||||
subtitle: Column(
|
|
||||||
crossAxisAlignment: CrossAxisAlignment.start,
|
|
||||||
children: [
|
|
||||||
Row(
|
|
||||||
children: [
|
|
||||||
RatingBar.builder(
|
|
||||||
initialRating:
|
|
||||||
event.scoresModel?.average ?? 0,
|
|
||||||
minRating: 1,
|
|
||||||
direction: Axis.horizontal,
|
|
||||||
allowHalfRating: true,
|
|
||||||
itemCount: 5,
|
|
||||||
itemSize: 25,
|
|
||||||
maxRating: 5,
|
|
||||||
itemPadding:
|
|
||||||
const EdgeInsets.symmetric(horizontal: 0),
|
|
||||||
itemBuilder: (context, _) => const Icon(
|
|
||||||
Icons.star,
|
|
||||||
color: Color(0xFF2BA4EC),
|
|
||||||
),
|
|
||||||
onRatingUpdate: (rating) {},
|
|
||||||
ignoreGestures: true,
|
|
||||||
),
|
|
||||||
const SizedBox(width: 5),
|
|
||||||
Text(
|
|
||||||
'(${event.scoresModel?.total.toString()}) ${event.scoresModel?.average.toStringAsFixed(1)}'),
|
|
||||||
],
|
|
||||||
),
|
|
||||||
Text(
|
|
||||||
'"${event.description}"',
|
|
||||||
style:
|
|
||||||
const TextStyle(fontStyle: FontStyle.italic),
|
|
||||||
),
|
|
||||||
],
|
|
||||||
),
|
|
||||||
trailing: const Column(
|
|
||||||
mainAxisAlignment: MainAxisAlignment.end,
|
|
||||||
children: [
|
|
||||||
Icon(Icons.keyboard_arrow_right),
|
|
||||||
],
|
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
},
|
}
|
||||||
|
},
|
||||||
|
leading: _statusIcon(event.status),
|
||||||
|
title: Column(
|
||||||
|
crossAxisAlignment: CrossAxisAlignment.start,
|
||||||
|
children: [
|
||||||
|
Text(
|
||||||
|
names[event.professionalId] ?? 'N/D',
|
||||||
|
style: const TextStyle(
|
||||||
|
color: Colors.black,
|
||||||
|
fontWeight: FontWeight.bold,
|
||||||
|
fontSize: 16,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
Text(
|
||||||
|
'${DateFormat('dd MMMM', 'es').format(DateTime.parse(event.day))} - ${DateFormat('h:mm a').format(DateTime.parse(event.range1Hour1))}',
|
||||||
|
style: TextStyle(color: Colors.grey[600], fontSize: 16),
|
||||||
|
),
|
||||||
|
],
|
||||||
),
|
),
|
||||||
),
|
subtitle: Column(
|
||||||
|
crossAxisAlignment: CrossAxisAlignment.start,
|
||||||
|
children: [
|
||||||
|
Row(
|
||||||
|
children: [
|
||||||
|
RatingBar.builder(
|
||||||
|
initialRating: event.scoresModel?.average ?? 0,
|
||||||
|
minRating: 1,
|
||||||
|
direction: Axis.horizontal,
|
||||||
|
allowHalfRating: true,
|
||||||
|
itemCount: 5,
|
||||||
|
itemSize: 25,
|
||||||
|
maxRating: 5,
|
||||||
|
itemPadding:
|
||||||
|
const EdgeInsets.symmetric(horizontal: 0),
|
||||||
|
itemBuilder: (context, _) => const Icon(
|
||||||
|
Icons.star,
|
||||||
|
color: Color(0xFF2BA4EC),
|
||||||
|
),
|
||||||
|
onRatingUpdate: (rating) {},
|
||||||
|
ignoreGestures: true,
|
||||||
|
),
|
||||||
|
const SizedBox(width: 5),
|
||||||
|
Text(
|
||||||
|
'(${event.scoresModel?.total.toString()}) ${event.scoresModel?.average.toStringAsFixed(1)}'),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
Text(
|
||||||
|
'"${event.description}"',
|
||||||
|
style: const TextStyle(fontStyle: FontStyle.italic),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
trailing: const Column(
|
||||||
|
mainAxisAlignment: MainAxisAlignment.end,
|
||||||
|
children: [Icon(Icons.keyboard_arrow_right)],
|
||||||
|
),
|
||||||
|
)),
|
||||||
],
|
],
|
||||||
);
|
);
|
||||||
},
|
},
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
Future<Map<String, String>> _loadProfessionalNames(
|
||||||
|
List<Event> events) async {
|
||||||
|
final ids = events.map((e) => e.professionalId).toSet();
|
||||||
|
final Map<String, String> names = {};
|
||||||
|
for (final id in ids) {
|
||||||
|
try {
|
||||||
|
final Map<String, dynamic> data =
|
||||||
|
await ApiService.instance.get('/users/$id');
|
||||||
|
names[id] = data['name'] ?? 'N/D';
|
||||||
|
} catch (_) {
|
||||||
|
names[id] = 'N/D';
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return names;
|
||||||
|
}
|
||||||
|
|
||||||
|
Widget _statusIcon(String status) {
|
||||||
|
switch (status) {
|
||||||
|
case 'aprobado':
|
||||||
|
return const Column(mainAxisAlignment: MainAxisAlignment.center, children: [
|
||||||
|
Icon(Icons.check, color: Colors.blue, size: 30),
|
||||||
|
Text('Aceptado', style: TextStyle(fontSize: 12)),
|
||||||
|
]);
|
||||||
|
case 'iniciado':
|
||||||
|
return const Column(mainAxisAlignment: MainAxisAlignment.center, children: [
|
||||||
|
Icon(Icons.access_time, color: Colors.blue, size: 30),
|
||||||
|
Text('Iniciado', style: TextStyle(fontSize: 12)),
|
||||||
|
]);
|
||||||
|
case 'pendiente':
|
||||||
|
return const Column(mainAxisAlignment: MainAxisAlignment.center, children: [
|
||||||
|
Icon(Icons.access_time_outlined, color: Colors.blue, size: 30),
|
||||||
|
Text('Pendiente', style: TextStyle(fontSize: 12)),
|
||||||
|
]);
|
||||||
|
case 'terminado':
|
||||||
|
return const Column(mainAxisAlignment: MainAxisAlignment.center, children: [
|
||||||
|
Icon(Icons.rocket_launch, color: Colors.blue, size: 30),
|
||||||
|
Text('Finalizado', style: TextStyle(fontSize: 12)),
|
||||||
|
]);
|
||||||
|
default:
|
||||||
|
return const Column(mainAxisAlignment: MainAxisAlignment.center, children: [
|
||||||
|
Icon(Icons.close, color: Colors.red, size: 30),
|
||||||
|
Text('Cancelado', style: TextStyle(fontSize: 12)),
|
||||||
|
]);
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,4 +1,3 @@
|
|||||||
import 'package:cloud_firestore/cloud_firestore.dart';
|
|
||||||
import 'package:flutter/cupertino.dart';
|
import 'package:flutter/cupertino.dart';
|
||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
import 'package:flutter_rating_bar/flutter_rating_bar.dart';
|
import 'package:flutter_rating_bar/flutter_rating_bar.dart';
|
||||||
@@ -10,13 +9,51 @@ import 'package:prosappco/src/models/event_model.dart';
|
|||||||
import 'package:prosappco/src/models/scores_model.dart';
|
import 'package:prosappco/src/models/scores_model.dart';
|
||||||
import 'package:prosappco/src/presentation/screens/cita.dart';
|
import 'package:prosappco/src/presentation/screens/cita.dart';
|
||||||
import 'package:prosappco/src/presentation/screens/score.dart';
|
import 'package:prosappco/src/presentation/screens/score.dart';
|
||||||
|
import 'package:prosappco/src/services/api_service.dart';
|
||||||
|
|
||||||
class MyServicesProScreen extends StatelessWidget {
|
class MyServicesProScreen extends StatefulWidget {
|
||||||
MyServicesProScreen({super.key});
|
MyServicesProScreen({super.key});
|
||||||
|
|
||||||
DateTime today = DateTime.now();
|
@override
|
||||||
|
State<MyServicesProScreen> createState() => _MyServicesProScreenState();
|
||||||
|
}
|
||||||
|
|
||||||
|
class _MyServicesProScreenState extends State<MyServicesProScreen> {
|
||||||
|
DateTime today = DateTime.now();
|
||||||
final uid = AuthenticationRepository.instance.getCurrentUserUid();
|
final uid = AuthenticationRepository.instance.getCurrentUserUid();
|
||||||
|
List<Event> eventos = [];
|
||||||
|
bool loading = true;
|
||||||
|
|
||||||
|
@override
|
||||||
|
void initState() {
|
||||||
|
super.initState();
|
||||||
|
_load();
|
||||||
|
}
|
||||||
|
|
||||||
|
Future<void> _load() async {
|
||||||
|
try {
|
||||||
|
final List<dynamic> data = await ApiService.instance.get(
|
||||||
|
'/services?professionalId=$uid&status=aprobado,denegado,iniciado,terminado');
|
||||||
|
final List<Event> loaded = [];
|
||||||
|
for (final e in data) {
|
||||||
|
final event = Event.fromJson(e as Map<String, dynamic>);
|
||||||
|
event.scoresModel =
|
||||||
|
await ScoresModel.scoreTo(event.userId, false, false);
|
||||||
|
loaded.add(event);
|
||||||
|
}
|
||||||
|
loaded.sort((a, b) {
|
||||||
|
if (a.timeStamp == null || b.timeStamp == null) return 0;
|
||||||
|
return a.timeStamp!.compareTo(b.timeStamp!);
|
||||||
|
});
|
||||||
|
if (mounted) setState(() {
|
||||||
|
eventos = loaded;
|
||||||
|
loading = false;
|
||||||
|
});
|
||||||
|
} catch (e) {
|
||||||
|
print('Error loading pro services: $e');
|
||||||
|
if (mounted) setState(() => loading = false);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
@override
|
@override
|
||||||
Widget build(BuildContext context) {
|
Widget build(BuildContext context) {
|
||||||
@@ -29,222 +66,139 @@ class MyServicesProScreen extends StatelessWidget {
|
|||||||
label: 'Mis servicios',
|
label: 'Mis servicios',
|
||||||
),
|
),
|
||||||
drawer: DrawerProfessional(),
|
drawer: DrawerProfessional(),
|
||||||
body: SingleChildScrollView(
|
body: loading
|
||||||
child: Column(
|
? const Center(child: CircularProgressIndicator())
|
||||||
children: [
|
: SingleChildScrollView(
|
||||||
_eventList(),
|
child: Column(children: [_eventList(context)]),
|
||||||
],
|
),
|
||||||
),
|
|
||||||
),
|
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
Widget _eventList() {
|
Widget _eventList(BuildContext context) {
|
||||||
return StreamBuilder<List<Event>>(
|
if (eventos.isEmpty) {
|
||||||
stream: FirebaseFirestore.instance
|
return const Padding(
|
||||||
.collection('services')
|
padding: EdgeInsets.symmetric(vertical: 50),
|
||||||
.where('professional_id', isEqualTo: uid)
|
child: Center(child: Text('No tienes citas')),
|
||||||
.where('status', whereIn: [
|
);
|
||||||
'aprobado',
|
}
|
||||||
'denegado',
|
return Column(
|
||||||
'iniciado',
|
children: [
|
||||||
'terminado',
|
...eventos.map(
|
||||||
])
|
(event) => ListTile(
|
||||||
.snapshots()
|
tileColor: event.status == 'denegado'
|
||||||
.asyncMap((snapshot) async {
|
? Colors.red[100]
|
||||||
try {
|
: Colors.blue[100],
|
||||||
List<Event> eventos = [];
|
onTap: () {
|
||||||
for (var element in snapshot.docs) {
|
if (event.status == 'terminado') {
|
||||||
final event = Event.fromJson(element.data());
|
if (event.professionalId == uid) {
|
||||||
event.scoresModel =
|
if (event.professionalScored) {
|
||||||
await ScoresModel.scoreTo(event.userId, false, false);
|
Navigator.push(
|
||||||
event.id = element.id;
|
context,
|
||||||
eventos.add(event);
|
CupertinoPageRoute(
|
||||||
}
|
builder: (_) => CitaScreen(evento: event),
|
||||||
return eventos;
|
),
|
||||||
} catch (e) {
|
);
|
||||||
print('Error getByProId $e');
|
|
||||||
return [];
|
|
||||||
}
|
|
||||||
}),
|
|
||||||
builder: (BuildContext context, AsyncSnapshot<List<Event>> snapshot) {
|
|
||||||
if (snapshot.connectionState == ConnectionState.waiting) {
|
|
||||||
return const Center(
|
|
||||||
child: CircularProgressIndicator(),
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
List<Event> eventos = [];
|
|
||||||
|
|
||||||
try {
|
|
||||||
eventos.addAll(snapshot.data!);
|
|
||||||
eventos.sort((a, b) => a.timeStamp!.compareTo(b.timeStamp!));
|
|
||||||
} catch (e) {
|
|
||||||
print("Error" + e.toString());
|
|
||||||
}
|
|
||||||
|
|
||||||
if (eventos.isEmpty) {
|
|
||||||
return const Padding(
|
|
||||||
padding: EdgeInsets.symmetric(vertical: 50),
|
|
||||||
child: Center(child: Text('No tienes citas')),
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
return Column(
|
|
||||||
children: [
|
|
||||||
...eventos.map(
|
|
||||||
(event) => ListTile(
|
|
||||||
tileColor: event.status == 'denegado'
|
|
||||||
? Colors.red[100]
|
|
||||||
: Colors.blue[100],
|
|
||||||
onTap: () {
|
|
||||||
if (event.status == 'terminado') {
|
|
||||||
if (event.professionalId == uid) {
|
|
||||||
if (event.professionalScored) {
|
|
||||||
Navigator.push(
|
|
||||||
context,
|
|
||||||
CupertinoPageRoute(
|
|
||||||
builder: (BuildContext context) {
|
|
||||||
return CitaScreen(evento: event);
|
|
||||||
},
|
|
||||||
),
|
|
||||||
);
|
|
||||||
} else {
|
|
||||||
Navigator.push(
|
|
||||||
context,
|
|
||||||
CupertinoPageRoute(
|
|
||||||
builder: (BuildContext context) {
|
|
||||||
return ScoreScreen(evento: event, pro: true);
|
|
||||||
},
|
|
||||||
),
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
} else {
|
} else {
|
||||||
Navigator.push(
|
Navigator.push(
|
||||||
context,
|
context,
|
||||||
CupertinoPageRoute(
|
CupertinoPageRoute(
|
||||||
builder: (BuildContext context) {
|
builder: (_) => ScoreScreen(evento: event, pro: true),
|
||||||
return CitaScreen(evento: event);
|
|
||||||
},
|
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
},
|
}
|
||||||
leading: event.status == 'aprobado'
|
} else {
|
||||||
? const Column(
|
Navigator.push(
|
||||||
mainAxisAlignment: MainAxisAlignment.center,
|
context,
|
||||||
children: [
|
CupertinoPageRoute(
|
||||||
Icon(
|
builder: (_) => CitaScreen(evento: event),
|
||||||
Icons.check,
|
),
|
||||||
color: Colors.blue,
|
);
|
||||||
size: 30,
|
}
|
||||||
),
|
},
|
||||||
Text('Aceptado', style: TextStyle(fontSize: 12)),
|
leading: _statusIcon(event.status),
|
||||||
],
|
title: Column(
|
||||||
)
|
crossAxisAlignment: CrossAxisAlignment.start,
|
||||||
: event.status == 'iniciado'
|
children: [
|
||||||
? const Column(
|
Text(
|
||||||
mainAxisAlignment: MainAxisAlignment.center,
|
event.title,
|
||||||
children: [
|
style: const TextStyle(
|
||||||
Icon(
|
color: Colors.black,
|
||||||
Icons.access_time,
|
fontWeight: FontWeight.bold,
|
||||||
color: Colors.blue,
|
fontSize: 16,
|
||||||
size: 30,
|
),
|
||||||
),
|
|
||||||
Text('Iniciado', style: TextStyle(fontSize: 12)),
|
|
||||||
],
|
|
||||||
)
|
|
||||||
: event.status == 'terminado'
|
|
||||||
? const Column(
|
|
||||||
mainAxisAlignment: MainAxisAlignment.center,
|
|
||||||
children: [
|
|
||||||
Icon(
|
|
||||||
Icons.rocket_launch,
|
|
||||||
color: Colors.blue,
|
|
||||||
size: 30,
|
|
||||||
),
|
|
||||||
Text('Finalizado',
|
|
||||||
style: TextStyle(fontSize: 12)),
|
|
||||||
],
|
|
||||||
)
|
|
||||||
: const Column(
|
|
||||||
mainAxisAlignment: MainAxisAlignment.center,
|
|
||||||
children: [
|
|
||||||
Icon(
|
|
||||||
Icons.close,
|
|
||||||
color: Colors.red,
|
|
||||||
size: 30,
|
|
||||||
),
|
|
||||||
Text('Cancelado',
|
|
||||||
style: TextStyle(fontSize: 12)),
|
|
||||||
],
|
|
||||||
),
|
|
||||||
title: Column(
|
|
||||||
crossAxisAlignment: CrossAxisAlignment.start,
|
|
||||||
children: [
|
|
||||||
Text(
|
|
||||||
event.title,
|
|
||||||
style: const TextStyle(
|
|
||||||
color: Colors.black,
|
|
||||||
fontWeight: FontWeight.bold,
|
|
||||||
fontSize: 16,
|
|
||||||
),
|
|
||||||
),
|
|
||||||
Text(
|
|
||||||
'${DateFormat('dd MMMM', 'es').format(DateTime.parse(event.day))} - ${DateFormat('h:mm a').format(DateTime.parse(event.range1Hour1))}',
|
|
||||||
style: TextStyle(
|
|
||||||
color: Colors.grey[600],
|
|
||||||
fontSize: 16,
|
|
||||||
),
|
|
||||||
),
|
|
||||||
],
|
|
||||||
),
|
),
|
||||||
subtitle: Column(
|
Text(
|
||||||
crossAxisAlignment: CrossAxisAlignment.start,
|
'${DateFormat('dd MMMM', 'es').format(DateTime.parse(event.day))} - ${DateFormat('h:mm a').format(DateTime.parse(event.range1Hour1))}',
|
||||||
children: [
|
style: TextStyle(color: Colors.grey[600], fontSize: 16),
|
||||||
Row(
|
|
||||||
children: [
|
|
||||||
RatingBar.builder(
|
|
||||||
initialRating: event.scoresModel?.average ?? 0,
|
|
||||||
minRating: 1,
|
|
||||||
direction: Axis.horizontal,
|
|
||||||
allowHalfRating: true,
|
|
||||||
itemCount: 5,
|
|
||||||
itemSize: 25,
|
|
||||||
maxRating: 5,
|
|
||||||
itemPadding:
|
|
||||||
const EdgeInsets.symmetric(horizontal: 0),
|
|
||||||
itemBuilder: (context, _) => const Icon(
|
|
||||||
Icons.star,
|
|
||||||
color: Color(0xFF2BA4EC),
|
|
||||||
),
|
|
||||||
onRatingUpdate: (rating) {},
|
|
||||||
ignoreGestures: true,
|
|
||||||
),
|
|
||||||
const SizedBox(width: 5),
|
|
||||||
Text(
|
|
||||||
'(${event.scoresModel?.total.toString()}) ${event.scoresModel?.average.toStringAsFixed(1)}'),
|
|
||||||
],
|
|
||||||
),
|
|
||||||
Text(
|
|
||||||
'"${event.description}"',
|
|
||||||
style: const TextStyle(fontStyle: FontStyle.italic),
|
|
||||||
),
|
|
||||||
],
|
|
||||||
),
|
),
|
||||||
trailing: const Column(
|
],
|
||||||
mainAxisAlignment: MainAxisAlignment.end,
|
|
||||||
children: [
|
|
||||||
Icon(Icons.keyboard_arrow_right),
|
|
||||||
],
|
|
||||||
),
|
|
||||||
),
|
|
||||||
),
|
),
|
||||||
],
|
subtitle: Column(
|
||||||
);
|
crossAxisAlignment: CrossAxisAlignment.start,
|
||||||
},
|
children: [
|
||||||
|
Row(
|
||||||
|
children: [
|
||||||
|
RatingBar.builder(
|
||||||
|
initialRating: event.scoresModel?.average ?? 0,
|
||||||
|
minRating: 1,
|
||||||
|
direction: Axis.horizontal,
|
||||||
|
allowHalfRating: true,
|
||||||
|
itemCount: 5,
|
||||||
|
itemSize: 25,
|
||||||
|
maxRating: 5,
|
||||||
|
itemPadding: const EdgeInsets.symmetric(horizontal: 0),
|
||||||
|
itemBuilder: (context, _) => const Icon(
|
||||||
|
Icons.star,
|
||||||
|
color: Color(0xFF2BA4EC),
|
||||||
|
),
|
||||||
|
onRatingUpdate: (rating) {},
|
||||||
|
ignoreGestures: true,
|
||||||
|
),
|
||||||
|
const SizedBox(width: 5),
|
||||||
|
Text(
|
||||||
|
'(${event.scoresModel?.total.toString()}) ${event.scoresModel?.average.toStringAsFixed(1)}'),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
Text(
|
||||||
|
'"${event.description}"',
|
||||||
|
style: const TextStyle(fontStyle: FontStyle.italic),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
trailing: const Column(
|
||||||
|
mainAxisAlignment: MainAxisAlignment.end,
|
||||||
|
children: [Icon(Icons.keyboard_arrow_right)],
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
],
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
Widget _statusIcon(String status) {
|
||||||
|
switch (status) {
|
||||||
|
case 'aprobado':
|
||||||
|
return const Column(mainAxisAlignment: MainAxisAlignment.center, children: [
|
||||||
|
Icon(Icons.check, color: Colors.blue, size: 30),
|
||||||
|
Text('Aceptado', style: TextStyle(fontSize: 12)),
|
||||||
|
]);
|
||||||
|
case 'iniciado':
|
||||||
|
return const Column(mainAxisAlignment: MainAxisAlignment.center, children: [
|
||||||
|
Icon(Icons.access_time, color: Colors.blue, size: 30),
|
||||||
|
Text('Iniciado', style: TextStyle(fontSize: 12)),
|
||||||
|
]);
|
||||||
|
case 'terminado':
|
||||||
|
return const Column(mainAxisAlignment: MainAxisAlignment.center, children: [
|
||||||
|
Icon(Icons.rocket_launch, color: Colors.blue, size: 30),
|
||||||
|
Text('Finalizado', style: TextStyle(fontSize: 12)),
|
||||||
|
]);
|
||||||
|
default:
|
||||||
|
return const Column(mainAxisAlignment: MainAxisAlignment.center, children: [
|
||||||
|
Icon(Icons.close, color: Colors.red, size: 30),
|
||||||
|
Text('Cancelado', style: TextStyle(fontSize: 12)),
|
||||||
|
]);
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,4 +1,3 @@
|
|||||||
import 'package:firebase_auth/firebase_auth.dart';
|
|
||||||
import 'package:flutter/foundation.dart';
|
import 'package:flutter/foundation.dart';
|
||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
import 'package:get/get.dart';
|
import 'package:get/get.dart';
|
||||||
@@ -14,18 +13,6 @@ class NewNumberScreen extends StatefulWidget {
|
|||||||
State<NewNumberScreen> createState() => _NewNumberScreenState();
|
State<NewNumberScreen> createState() => _NewNumberScreenState();
|
||||||
}
|
}
|
||||||
|
|
||||||
// Actualizar número de teléfono en Firebase
|
|
||||||
Future<void> updatePhoneNumber(String verificationId, String smsCode) async {
|
|
||||||
try {
|
|
||||||
PhoneAuthCredential credential = PhoneAuthProvider.credential(
|
|
||||||
verificationId: verificationId, smsCode: smsCode);
|
|
||||||
await FirebaseAuth.instance.currentUser!.updatePhoneNumber(credential);
|
|
||||||
print("Phone number updated successfully");
|
|
||||||
} catch (e) {
|
|
||||||
print("Error updating phone number: $e");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
class _NewNumberScreenState extends State<NewNumberScreen> {
|
class _NewNumberScreenState extends State<NewNumberScreen> {
|
||||||
final controller = Get.put(NewPhoneController());
|
final controller = Get.put(NewPhoneController());
|
||||||
String completePhoneNumber = '';
|
String completePhoneNumber = '';
|
||||||
@@ -149,7 +136,7 @@ class _NewNumberScreenState extends State<NewNumberScreen> {
|
|||||||
controller.updatePhoneNumber(
|
controller.updatePhoneNumber(
|
||||||
completePhoneNumber.toString());
|
completePhoneNumber.toString());
|
||||||
},
|
},
|
||||||
label: 'Enviar código'),
|
label: 'Actualizar número'),
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
],
|
],
|
||||||
@@ -262,7 +249,7 @@ class _NewNumberScreenState extends State<NewNumberScreen> {
|
|||||||
controller.updatePhoneNumber(
|
controller.updatePhoneNumber(
|
||||||
completePhoneNumber.toString());
|
completePhoneNumber.toString());
|
||||||
},
|
},
|
||||||
label: 'Enviar código'),
|
label: 'Actualizar número'),
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
],
|
],
|
||||||
|
|||||||
@@ -1,4 +1,3 @@
|
|||||||
import 'package:firebase_auth/firebase_auth.dart';
|
|
||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
import 'package:get/get.dart';
|
import 'package:get/get.dart';
|
||||||
import 'package:prosappco/src/components/pop_appbar.dart';
|
import 'package:prosappco/src/components/pop_appbar.dart';
|
||||||
@@ -17,59 +16,13 @@ class _NewPasswordScreenState extends State<NewPasswordScreen> {
|
|||||||
bool _obscureText = true;
|
bool _obscureText = true;
|
||||||
bool _obscureText2 = true;
|
bool _obscureText2 = true;
|
||||||
|
|
||||||
final FirebaseAuth _auth = FirebaseAuth.instance;
|
void updatePassword(String currentPassword, String newPassword) {
|
||||||
|
// ponytail: password change requires backend endpoint; show support message
|
||||||
Future<void> updatePassword(
|
Get.snackbar(
|
||||||
String currentPassword, String newPassword) async {
|
'Cambio de contraseña',
|
||||||
final User user = _auth.currentUser!;
|
'Para cambiar tu contraseña contacta a soporte.',
|
||||||
|
snackPosition: SnackPosition.BOTTOM,
|
||||||
final credential = EmailAuthProvider.credential(
|
|
||||||
email: user.email!,
|
|
||||||
password: currentPassword,
|
|
||||||
);
|
);
|
||||||
|
|
||||||
try {
|
|
||||||
if (newPassword == currentPassword) {
|
|
||||||
Get.snackbar(
|
|
||||||
'Misma contraseña',
|
|
||||||
'La nueva contraseña debe ser distinta a la contraseña actual.',
|
|
||||||
snackPosition: SnackPosition.BOTTOM,
|
|
||||||
);
|
|
||||||
return;
|
|
||||||
} else if (newPassword.isEmpty) {
|
|
||||||
Get.snackbar(
|
|
||||||
'Ingrese una contraseña valida',
|
|
||||||
'La nueva contraseña no puede estar vacia.',
|
|
||||||
snackPosition: SnackPosition.BOTTOM,
|
|
||||||
);
|
|
||||||
return;
|
|
||||||
} else {
|
|
||||||
await user.reauthenticateWithCredential(credential);
|
|
||||||
try {
|
|
||||||
await user.updatePassword(newPassword);
|
|
||||||
// Muestra un mensaje de éxito
|
|
||||||
Get.snackbar(
|
|
||||||
'Contraseña actualizada',
|
|
||||||
'Tu contraseña ha sido cambiada con éxito.',
|
|
||||||
snackPosition: SnackPosition.BOTTOM,
|
|
||||||
);
|
|
||||||
} catch (e) {
|
|
||||||
print("Error al verificar la contraseña actual: $e");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
} catch (e) {
|
|
||||||
Get.snackbar(
|
|
||||||
'Contraseña incorrecta',
|
|
||||||
'Ha ocurrido un error al actualizar la contraseña. Asegúrate de ingresar correctamente la contraseña actual.',
|
|
||||||
snackPosition: SnackPosition.BOTTOM,
|
|
||||||
);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@override
|
|
||||||
void initState() {
|
|
||||||
super.initState();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@override
|
@override
|
||||||
|
|||||||
@@ -1,25 +1,13 @@
|
|||||||
import 'package:cloud_firestore/cloud_firestore.dart';
|
|
||||||
import 'package:diacritic/diacritic.dart';
|
import 'package:diacritic/diacritic.dart';
|
||||||
import 'package:firebase_auth/firebase_auth.dart';
|
|
||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
import 'package:prosappco/src/authentication/authentication_repository.dart';
|
import 'package:prosappco/src/authentication/authentication_repository.dart';
|
||||||
import 'package:prosappco/src/components/pop_appbar.dart';
|
import 'package:prosappco/src/components/pop_appbar.dart';
|
||||||
|
import 'package:prosappco/src/services/api_service.dart';
|
||||||
final CollectionReference professionsCollection =
|
|
||||||
FirebaseFirestore.instance.collection('professions');
|
|
||||||
|
|
||||||
Future<List<String>> getProfessions() async {
|
Future<List<String>> getProfessions() async {
|
||||||
try {
|
try {
|
||||||
DocumentSnapshot<Object?> profession =
|
final List<dynamic> data = await ApiService.instance.get('/professions');
|
||||||
await professionsCollection.doc('professions').get();
|
return data.map((e) => (e['name'] ?? e.toString()) as String).toList();
|
||||||
|
|
||||||
Map<String, dynamic> data = profession.data() as Map<String, dynamic>;
|
|
||||||
|
|
||||||
var professionsList = (data['professions'] as List<dynamic>)
|
|
||||||
.map((e) => e.toString())
|
|
||||||
.toList();
|
|
||||||
|
|
||||||
return professionsList;
|
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
print('$e');
|
print('$e');
|
||||||
}
|
}
|
||||||
@@ -37,7 +25,6 @@ class ProfessionScreen extends StatefulWidget {
|
|||||||
class _ProfessionScreenState extends State<ProfessionScreen> {
|
class _ProfessionScreenState extends State<ProfessionScreen> {
|
||||||
List<String>? filteredProfessions;
|
List<String>? filteredProfessions;
|
||||||
TextEditingController searchController = TextEditingController();
|
TextEditingController searchController = TextEditingController();
|
||||||
final User? user = FirebaseAuth.instance.currentUser;
|
|
||||||
List<String>? _professions;
|
List<String>? _professions;
|
||||||
final ScrollController _scrollController = ScrollController();
|
final ScrollController _scrollController = ScrollController();
|
||||||
final uid = AuthenticationRepository.instance.getCurrentUserUid();
|
final uid = AuthenticationRepository.instance.getCurrentUserUid();
|
||||||
@@ -73,65 +60,28 @@ class _ProfessionScreenState extends State<ProfessionScreen> {
|
|||||||
|
|
||||||
Future<void> updateProfession(String profession) async {
|
Future<void> updateProfession(String profession) async {
|
||||||
try {
|
try {
|
||||||
await FirebaseFirestore.instance
|
await ApiService.instance.patch('/users/me', {'profession': profession});
|
||||||
.collection('users')
|
|
||||||
.doc(uid)
|
|
||||||
.update({'profesion': profession});
|
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
try {
|
|
||||||
await FirebaseFirestore.instance
|
|
||||||
.collection('users')
|
|
||||||
.doc(uid)
|
|
||||||
.set({'profesion': profession});
|
|
||||||
} catch (e) {
|
|
||||||
print('Error al agregar la profesion: $e');
|
|
||||||
}
|
|
||||||
|
|
||||||
print('Error al actualizar la profesion: $e');
|
print('Error al actualizar la profesion: $e');
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
Future<void> saveProfession(String newProfession) async {
|
Future<void> saveProfession(String newProfession) async {
|
||||||
final DocumentReference professionsDocRef =
|
|
||||||
professionsCollection.doc('professions');
|
|
||||||
|
|
||||||
try {
|
try {
|
||||||
final DocumentSnapshot<Object?> profession =
|
// Add profession via API (POST /professions or use existing endpoint)
|
||||||
await professionsDocRef.get();
|
// For now just add it locally to the list
|
||||||
|
|
||||||
Map<String, dynamic> data = profession.data() as Map<String, dynamic>;
|
|
||||||
|
|
||||||
List<String> professions = [];
|
|
||||||
|
|
||||||
if (data['professions'] != null) {
|
|
||||||
professions = List<String>.from(data['professions']);
|
|
||||||
}
|
|
||||||
|
|
||||||
professions.add(newProfession);
|
|
||||||
|
|
||||||
await professionsDocRef.set({
|
|
||||||
'professions': professions,
|
|
||||||
}, SetOptions(merge: true));
|
|
||||||
|
|
||||||
setState(() {
|
setState(() {
|
||||||
getProfessions().then((List<String> element) => setState(() {
|
_professions ??= [];
|
||||||
_professions = element;
|
_professions!.add(newProfession);
|
||||||
filteredProfessions = element;
|
filteredProfessions = List.from(_professions!);
|
||||||
int newIndex = professions.indexOf(newProfession);
|
isNewProfessionAdded = true;
|
||||||
if (newIndex != -1) {
|
Future.delayed(const Duration(seconds: 2), () {
|
||||||
_scrollController.animateTo(
|
if (mounted) {
|
||||||
newIndex * 50.0,
|
setState(() {
|
||||||
duration: const Duration(milliseconds: 600),
|
isNewProfessionAdded = false;
|
||||||
curve: Curves.easeIn,
|
});
|
||||||
);
|
}
|
||||||
}
|
});
|
||||||
isNewProfessionAdded = true;
|
|
||||||
Future.delayed(const Duration(seconds: 2), () {
|
|
||||||
setState(() {
|
|
||||||
isNewProfessionAdded = false;
|
|
||||||
});
|
|
||||||
});
|
|
||||||
}));
|
|
||||||
});
|
});
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
print('Error al guardar la profesión: $e');
|
print('Error al guardar la profesión: $e');
|
||||||
|
|||||||
@@ -1,6 +1,4 @@
|
|||||||
import 'package:cloud_firestore/cloud_firestore.dart';
|
|
||||||
import 'package:diacritic/diacritic.dart';
|
import 'package:diacritic/diacritic.dart';
|
||||||
import 'package:firebase_storage/firebase_storage.dart';
|
|
||||||
import 'package:flutter/cupertino.dart';
|
import 'package:flutter/cupertino.dart';
|
||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
import 'package:intl/intl.dart';
|
import 'package:intl/intl.dart';
|
||||||
@@ -9,10 +7,10 @@ import 'package:prosappco/src/components/photo_view.dart';
|
|||||||
import 'package:prosappco/src/components/pop_appbar.dart';
|
import 'package:prosappco/src/components/pop_appbar.dart';
|
||||||
import 'package:prosappco/src/models/professional_model.dart';
|
import 'package:prosappco/src/models/professional_model.dart';
|
||||||
import 'package:prosappco/src/models/setting_model.dart';
|
import 'package:prosappco/src/models/setting_model.dart';
|
||||||
import 'package:prosappco/src/models/user_model.dart';
|
|
||||||
import 'package:prosappco/src/presentation/screens/calendar_pro.dart';
|
import 'package:prosappco/src/presentation/screens/calendar_pro.dart';
|
||||||
import 'package:prosappco/src/presentation/screens/professional_info.dart';
|
import 'package:prosappco/src/presentation/screens/professional_info.dart';
|
||||||
import 'package:prosappco/src/presentation/widgets/shared/loading_item_list.dart';
|
import 'package:prosappco/src/presentation/widgets/shared/loading_item_list.dart';
|
||||||
|
import 'package:prosappco/src/services/api_service.dart';
|
||||||
import '../../models/scores_model.dart';
|
import '../../models/scores_model.dart';
|
||||||
|
|
||||||
class ProfessionalScreen extends StatefulWidget {
|
class ProfessionalScreen extends StatefulWidget {
|
||||||
@@ -26,64 +24,12 @@ class ProfessionalScreen extends StatefulWidget {
|
|||||||
State<ProfessionalScreen> createState() => _ProfessionalScreenState();
|
State<ProfessionalScreen> createState() => _ProfessionalScreenState();
|
||||||
}
|
}
|
||||||
|
|
||||||
var _photo = '.../images/perfil-2.png';
|
|
||||||
final FirebaseStorage storage = FirebaseStorage.instance;
|
|
||||||
|
|
||||||
final CollectionReference usersCollection =
|
|
||||||
FirebaseFirestore.instance.collection('users');
|
|
||||||
|
|
||||||
class _ProfessionalScreenState extends State<ProfessionalScreen> {
|
class _ProfessionalScreenState extends State<ProfessionalScreen> {
|
||||||
UserModel? userme;
|
|
||||||
SettingModel? settings;
|
SettingModel? settings;
|
||||||
|
List<Professional>? filteredProfessionals;
|
||||||
@override
|
List<Professional>? _professionals;
|
||||||
void initState() {
|
TextEditingController searchController = TextEditingController();
|
||||||
super.initState();
|
final uid = AuthenticationRepository.instance.getCurrentUserUid();
|
||||||
|
|
||||||
if (userme == null) {
|
|
||||||
UserModel.getUser(uid.toString()).then(
|
|
||||||
(UserModel s) => setState(() => userme = s),
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (settings == null) {
|
|
||||||
SettingModel.getSettings().then(
|
|
||||||
(SettingModel value) => setState(() {
|
|
||||||
settings = value;
|
|
||||||
|
|
||||||
print('initState settings: $settings');
|
|
||||||
}),
|
|
||||||
);
|
|
||||||
}
|
|
||||||
searchController.addListener(() {
|
|
||||||
setState(() {
|
|
||||||
if (_professionals != null) {
|
|
||||||
if (searchController.text.isEmpty) {
|
|
||||||
filteredProfessionals = _professionals!
|
|
||||||
.where((professional) => professional.id != uid)
|
|
||||||
.toList();
|
|
||||||
} else {
|
|
||||||
filteredProfessionals = _professionals!
|
|
||||||
.where((professional) =>
|
|
||||||
removeDiacritics(professional.name).toLowerCase().contains(
|
|
||||||
removeDiacritics(
|
|
||||||
searchController.text.toLowerCase())) &&
|
|
||||||
professional.id != uid)
|
|
||||||
.toList();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
if (_professionals == null) {
|
|
||||||
getProfessionals().then((List<Professional> element) => setState(() {
|
|
||||||
_professionals = element;
|
|
||||||
filteredProfessionals = element;
|
|
||||||
}));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
var photo = '.../images/perfil-2.png';
|
|
||||||
|
|
||||||
String formatCurrency(int number) {
|
String formatCurrency(int number) {
|
||||||
final formatter =
|
final formatter =
|
||||||
@@ -91,8 +37,90 @@ class _ProfessionalScreenState extends State<ProfessionalScreen> {
|
|||||||
return '\$${formatter.format(number)}';
|
return '\$${formatter.format(number)}';
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
void initState() {
|
||||||
|
super.initState();
|
||||||
|
|
||||||
|
SettingModel.getSettings().then((s) {
|
||||||
|
if (mounted) setState(() => settings = s);
|
||||||
|
});
|
||||||
|
|
||||||
|
searchController.addListener(() {
|
||||||
|
setState(() {
|
||||||
|
if (_professionals != null) {
|
||||||
|
if (searchController.text.isEmpty) {
|
||||||
|
filteredProfessionals =
|
||||||
|
_professionals!.where((p) => p.id != uid).toList();
|
||||||
|
} else {
|
||||||
|
filteredProfessionals = _professionals!
|
||||||
|
.where((p) =>
|
||||||
|
removeDiacritics(p.name)
|
||||||
|
.toLowerCase()
|
||||||
|
.contains(removeDiacritics(
|
||||||
|
searchController.text.toLowerCase())) &&
|
||||||
|
p.id != uid)
|
||||||
|
.toList();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
_loadProfessionals();
|
||||||
|
}
|
||||||
|
|
||||||
|
Future<void> _loadProfessionals() async {
|
||||||
|
try {
|
||||||
|
final String query = widget.profession.isNotEmpty
|
||||||
|
? '/professionals?profession=${Uri.encodeComponent(widget.profession)}'
|
||||||
|
: '/professionals';
|
||||||
|
final List<dynamic> data = await ApiService.instance.get(query);
|
||||||
|
|
||||||
|
final List<Professional> loaded = [];
|
||||||
|
for (final e in data) {
|
||||||
|
final json = e as Map<String, dynamic>;
|
||||||
|
if (json['id'] == uid) continue;
|
||||||
|
|
||||||
|
final List<String> especializaciones =
|
||||||
|
((json['especialidades'] ?? json['specialties'] ?? []) as List)
|
||||||
|
.map((x) => x.toString())
|
||||||
|
.toList();
|
||||||
|
|
||||||
|
final scores =
|
||||||
|
await ScoresModel.scoreTo(json['id']?.toString(), true, false);
|
||||||
|
|
||||||
|
loaded.add(Professional(
|
||||||
|
id: json['id']?.toString() ?? '',
|
||||||
|
photoUrl: json['picture'] ?? json['photo_url'],
|
||||||
|
name: json['name'] ?? '',
|
||||||
|
professionName: json['profession'] ?? json['profesion'] ?? '',
|
||||||
|
cityName: json['city'] ?? '',
|
||||||
|
professionalEspecializado: especializaciones,
|
||||||
|
ubicacion: json['ubicacion'] ?? '',
|
||||||
|
realAddress: json['address'] ?? '',
|
||||||
|
latitude: (json['latitude'] ?? 0).toDouble(),
|
||||||
|
longitude: (json['longitude'] ?? 0).toDouble(),
|
||||||
|
scores: scores,
|
||||||
|
tarifa: json['rate'] != null
|
||||||
|
? double.tryParse(json['rate'].toString())?.toInt()
|
||||||
|
: json['tarifas'],
|
||||||
|
token: json['token'],
|
||||||
|
));
|
||||||
|
}
|
||||||
|
|
||||||
|
if (mounted) {
|
||||||
|
setState(() {
|
||||||
|
_professionals = loaded;
|
||||||
|
filteredProfessionals = loaded.where((p) => p.id != uid).toList();
|
||||||
|
});
|
||||||
|
}
|
||||||
|
} catch (e) {
|
||||||
|
print('Error loading professionals: $e');
|
||||||
|
if (mounted) setState(() => filteredProfessionals = []);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
Future<String?> _showChoiceDialog(BuildContext context) async {
|
Future<String?> _showChoiceDialog(BuildContext context) async {
|
||||||
String? selectedOption = await showDialog(
|
return showDialog<String>(
|
||||||
context: context,
|
context: context,
|
||||||
builder: (BuildContext context) {
|
builder: (BuildContext context) {
|
||||||
return AlertDialog(
|
return AlertDialog(
|
||||||
@@ -105,9 +133,7 @@ class _ProfessionalScreenState extends State<ProfessionalScreen> {
|
|||||||
"A domicilio",
|
"A domicilio",
|
||||||
style: TextStyle(color: Color(0xFF2BA4EC)),
|
style: TextStyle(color: Color(0xFF2BA4EC)),
|
||||||
),
|
),
|
||||||
onTap: () {
|
onTap: () => Navigator.of(context).pop("domicilio"),
|
||||||
Navigator.of(context).pop("domicilio");
|
|
||||||
},
|
|
||||||
),
|
),
|
||||||
const Divider(color: Colors.black54),
|
const Divider(color: Colors.black54),
|
||||||
GestureDetector(
|
GestureDetector(
|
||||||
@@ -116,9 +142,7 @@ class _ProfessionalScreenState extends State<ProfessionalScreen> {
|
|||||||
"En sitio",
|
"En sitio",
|
||||||
style: TextStyle(color: Color(0xFF2BA4EC)),
|
style: TextStyle(color: Color(0xFF2BA4EC)),
|
||||||
),
|
),
|
||||||
onTap: () {
|
onTap: () => Navigator.of(context).pop("sitio"),
|
||||||
Navigator.of(context).pop("sitio");
|
|
||||||
},
|
|
||||||
),
|
),
|
||||||
],
|
],
|
||||||
),
|
),
|
||||||
@@ -126,90 +150,6 @@ class _ProfessionalScreenState extends State<ProfessionalScreen> {
|
|||||||
);
|
);
|
||||||
},
|
},
|
||||||
);
|
);
|
||||||
return selectedOption;
|
|
||||||
}
|
|
||||||
|
|
||||||
List<Professional>? filteredProfessionals;
|
|
||||||
|
|
||||||
TextEditingController searchController = TextEditingController();
|
|
||||||
|
|
||||||
final uid = AuthenticationRepository.instance.getCurrentUserUid();
|
|
||||||
List<Professional>? _professionals;
|
|
||||||
|
|
||||||
Future<List<Professional>> getProfessionals() async {
|
|
||||||
List<Professional> professionals = [];
|
|
||||||
|
|
||||||
try {
|
|
||||||
QuerySnapshot users = await usersCollection.get();
|
|
||||||
for (DocumentSnapshot user in users.docs) {
|
|
||||||
Map<String, dynamic> data = user.data() as Map<String, dynamic>;
|
|
||||||
|
|
||||||
_photo = await AuthenticationRepository.instance.getPhoto(user.id);
|
|
||||||
|
|
||||||
if (data['estado'] == 'activo') {
|
|
||||||
if (user.id != uid) {
|
|
||||||
List<String> especializaciones;
|
|
||||||
|
|
||||||
especializaciones = (data['especializaciones'] as List<dynamic>)
|
|
||||||
.map((e) => e.toString())
|
|
||||||
.toList();
|
|
||||||
|
|
||||||
if (settings?.domicilios == false) {
|
|
||||||
if (data['ubicacion'] == 'ambos' ||
|
|
||||||
data['ubicacion'] == 'sitio') {
|
|
||||||
if (widget.profession == data['profesion'] ||
|
|
||||||
(widget.profession == '' &&
|
|
||||||
userme?.city == data['city'] &&
|
|
||||||
data['ubicacion'] != null)) {
|
|
||||||
Professional professional = Professional(
|
|
||||||
id: user.id,
|
|
||||||
name: data['name'],
|
|
||||||
professionName: data['profesion'],
|
|
||||||
cityName: data['city'],
|
|
||||||
professionalRef: storage.ref().child(_photo),
|
|
||||||
professionalEspecializado: especializaciones,
|
|
||||||
ubicacion: data['ubicacion'] ?? '',
|
|
||||||
realAddress: data['address'] ?? '',
|
|
||||||
latitude: data['latitude'] ?? 0,
|
|
||||||
longitude: data['longitude'] ?? 0,
|
|
||||||
scores: await ScoresModel.scoreTo(user.id, true, true),
|
|
||||||
tarifa: data['tarifas'] ?? 0,
|
|
||||||
token: data['token'] ?? '',
|
|
||||||
);
|
|
||||||
professionals.add(professional);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
if (widget.profession == data['profesion'] ||
|
|
||||||
(widget.profession == '' &&
|
|
||||||
userme?.city == data['city'] &&
|
|
||||||
data['ubicacion'] != null)) {
|
|
||||||
Professional professional = Professional(
|
|
||||||
id: user.id,
|
|
||||||
name: data['name'],
|
|
||||||
professionName: data['profesion'],
|
|
||||||
cityName: data['city'],
|
|
||||||
professionalRef: storage.ref().child(_photo),
|
|
||||||
professionalEspecializado: especializaciones,
|
|
||||||
ubicacion: data['ubicacion'] ?? '',
|
|
||||||
realAddress: data['address'] ?? '',
|
|
||||||
latitude: data['latitude'] ?? 0,
|
|
||||||
longitude: data['longitude'] ?? 0,
|
|
||||||
scores: await ScoresModel.scoreTo(user.id, true, true),
|
|
||||||
tarifa: data['tarifas'] ?? 0,
|
|
||||||
token: data['token'] ?? '',
|
|
||||||
);
|
|
||||||
professionals.add(professional);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
} catch (e) {
|
|
||||||
print('Error al obtener profesionales: $e');
|
|
||||||
}
|
|
||||||
|
|
||||||
return professionals;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@override
|
@override
|
||||||
@@ -218,9 +158,7 @@ class _ProfessionalScreenState extends State<ProfessionalScreen> {
|
|||||||
return SafeArea(
|
return SafeArea(
|
||||||
child: Scaffold(
|
child: Scaffold(
|
||||||
appBar: PopAppbar(
|
appBar: PopAppbar(
|
||||||
onPressed: () {
|
onPressed: () => Navigator.pop(context),
|
||||||
Navigator.pop(context);
|
|
||||||
},
|
|
||||||
label: 'Seleccione un profesional',
|
label: 'Seleccione un profesional',
|
||||||
),
|
),
|
||||||
body: Column(
|
body: Column(
|
||||||
@@ -249,18 +187,12 @@ class _ProfessionalScreenState extends State<ProfessionalScreen> {
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (_photo == null || _photo.isEmpty) {
|
final professionals = filteredProfessionals!;
|
||||||
_photo = '.../images/perfil-2.png';
|
|
||||||
}
|
|
||||||
|
|
||||||
var professionals = filteredProfessionals!;
|
|
||||||
|
|
||||||
return SafeArea(
|
return SafeArea(
|
||||||
child: Scaffold(
|
child: Scaffold(
|
||||||
appBar: PopAppbar(
|
appBar: PopAppbar(
|
||||||
onPressed: () {
|
onPressed: () => Navigator.pop(context),
|
||||||
Navigator.pop(context);
|
|
||||||
},
|
|
||||||
label: 'Seleccione un profesional'),
|
label: 'Seleccione un profesional'),
|
||||||
body: Column(
|
body: Column(
|
||||||
children: [
|
children: [
|
||||||
@@ -280,27 +212,23 @@ class _ProfessionalScreenState extends State<ProfessionalScreen> {
|
|||||||
padding:
|
padding:
|
||||||
const EdgeInsets.only(left: 20, right: 20, top: 50),
|
const EdgeInsets.only(left: 20, right: 20, top: 50),
|
||||||
child:
|
child:
|
||||||
Text('Aún no tenemos ningun(a) ${widget.profession}'),
|
Text('Aún no tenemos ningun(a) ${widget.profession}'),
|
||||||
))
|
))
|
||||||
: Expanded(
|
: Expanded(
|
||||||
child: ListView.builder(
|
child: ListView.builder(
|
||||||
itemCount: professionals.length,
|
itemCount: professionals.length,
|
||||||
itemBuilder: (BuildContext context, int index) {
|
itemBuilder: (BuildContext context, int index) {
|
||||||
|
final pro = professionals[index];
|
||||||
return ListTile(
|
return ListTile(
|
||||||
leading: GestureDetector(
|
leading: GestureDetector(
|
||||||
onTap: () {
|
onTap: () {
|
||||||
Navigator.of(context).push(
|
Navigator.of(context).push(CupertinoPageRoute(
|
||||||
CupertinoPageRoute(
|
builder: (_) =>
|
||||||
builder: (BuildContext context) {
|
ProfessionalInfoScreen(professional: pro),
|
||||||
return ProfessionalInfoScreen(
|
));
|
||||||
professional: professionals[index],
|
|
||||||
);
|
|
||||||
},
|
|
||||||
),
|
|
||||||
);
|
|
||||||
},
|
},
|
||||||
child: ReferencePhoto(
|
child: ReferencePhoto(
|
||||||
ref: professionals[index].professionalRef,
|
ref: pro.photoUrl,
|
||||||
sizeCircle: 50,
|
sizeCircle: 50,
|
||||||
size: 50,
|
size: 50,
|
||||||
sizeIcon: 35,
|
sizeIcon: 35,
|
||||||
@@ -309,32 +237,25 @@ class _ProfessionalScreenState extends State<ProfessionalScreen> {
|
|||||||
trailing: GestureDetector(
|
trailing: GestureDetector(
|
||||||
child: const Icon(Icons.keyboard_arrow_right),
|
child: const Icon(Icons.keyboard_arrow_right),
|
||||||
onTap: () {
|
onTap: () {
|
||||||
Navigator.of(context).push(
|
Navigator.of(context).push(CupertinoPageRoute(
|
||||||
CupertinoPageRoute(
|
builder: (_) =>
|
||||||
builder: (BuildContext context) {
|
ProfessionalInfoScreen(professional: pro),
|
||||||
return ProfessionalInfoScreen(
|
));
|
||||||
professional: professionals[index],
|
|
||||||
);
|
|
||||||
},
|
|
||||||
),
|
|
||||||
);
|
|
||||||
},
|
},
|
||||||
),
|
),
|
||||||
title: RichText(
|
title: RichText(
|
||||||
text: TextSpan(
|
text: TextSpan(
|
||||||
style: const TextStyle(
|
style: const TextStyle(
|
||||||
fontSize: 15.0,
|
fontSize: 15.0, color: Colors.black),
|
||||||
color: Colors.black,
|
|
||||||
),
|
|
||||||
children: <TextSpan>[
|
children: <TextSpan>[
|
||||||
TextSpan(
|
TextSpan(
|
||||||
text: '${professionals[index].name}, ',
|
text: '${pro.name}, ',
|
||||||
style: const TextStyle(
|
style: const TextStyle(
|
||||||
fontWeight: FontWeight.bold),
|
fontWeight: FontWeight.bold),
|
||||||
),
|
),
|
||||||
TextSpan(
|
TextSpan(
|
||||||
text:
|
text:
|
||||||
"${professionals[index].professionName}, ${professionals[index].cityName}",
|
"${pro.professionName}, ${pro.cityName}",
|
||||||
style: TextStyle(color: Colors.grey[600]),
|
style: TextStyle(color: Colors.grey[600]),
|
||||||
),
|
),
|
||||||
],
|
],
|
||||||
@@ -344,7 +265,7 @@ class _ProfessionalScreenState extends State<ProfessionalScreen> {
|
|||||||
crossAxisAlignment: CrossAxisAlignment.start,
|
crossAxisAlignment: CrossAxisAlignment.start,
|
||||||
children: [
|
children: [
|
||||||
settings?.tarifas == true
|
settings?.tarifas == true
|
||||||
? professionals[index].tarifa == 0
|
? pro.tarifa == 0
|
||||||
? const SizedBox()
|
? const SizedBox()
|
||||||
: Container(
|
: Container(
|
||||||
padding: const EdgeInsets.symmetric(
|
padding: const EdgeInsets.symmetric(
|
||||||
@@ -355,9 +276,7 @@ class _ProfessionalScreenState extends State<ProfessionalScreen> {
|
|||||||
color: const Color(0xFFD6F4FF),
|
color: const Color(0xFFD6F4FF),
|
||||||
),
|
),
|
||||||
child: Text(
|
child: Text(
|
||||||
formatCurrency(
|
formatCurrency(pro.tarifa ?? 0),
|
||||||
professionals[index].tarifa ??
|
|
||||||
0),
|
|
||||||
style: TextStyle(
|
style: TextStyle(
|
||||||
color: Colors.grey[850],
|
color: Colors.grey[850],
|
||||||
fontWeight: FontWeight.w600,
|
fontWeight: FontWeight.w600,
|
||||||
@@ -365,72 +284,56 @@ class _ProfessionalScreenState extends State<ProfessionalScreen> {
|
|||||||
),
|
),
|
||||||
)
|
)
|
||||||
: const SizedBox(),
|
: const SizedBox(),
|
||||||
professionals[index].ubicacion == 'ambos' &&
|
pro.ubicacion == 'ambos' &&
|
||||||
settings?.domicilios == true
|
settings?.domicilios == true
|
||||||
? const Text(
|
? const Text(
|
||||||
'Disponibilidad a domicilio y en sitio',
|
'Disponibilidad a domicilio y en sitio',
|
||||||
style: TextStyle(
|
style: TextStyle(color: Colors.blue),
|
||||||
color: Colors.blue,
|
|
||||||
),
|
|
||||||
)
|
)
|
||||||
: professionals[index].ubicacion == 'sitio' ||
|
: pro.ubicacion == 'sitio' ||
|
||||||
settings?.domicilios == false
|
settings?.domicilios == false
|
||||||
? const Text(
|
? const Text(
|
||||||
'Disponibilidad en sitio',
|
'Disponibilidad en sitio',
|
||||||
style: TextStyle(
|
style: TextStyle(color: Colors.blue),
|
||||||
color: Colors.blue,
|
|
||||||
),
|
|
||||||
)
|
)
|
||||||
: const Text(
|
: const Text(
|
||||||
'Disponibilidad a domicilio',
|
'Disponibilidad a domicilio',
|
||||||
style: TextStyle(
|
style: TextStyle(color: Colors.blue),
|
||||||
color: Colors.blue,
|
|
||||||
),
|
|
||||||
),
|
),
|
||||||
],
|
],
|
||||||
),
|
),
|
||||||
onTap: () async {
|
onTap: () async {
|
||||||
if (professionals[index].ubicacion == 'ambos' &&
|
if (pro.ubicacion == 'ambos' &&
|
||||||
settings?.domicilios == true) {
|
settings?.domicilios == true) {
|
||||||
_showChoiceDialog(context)
|
_showChoiceDialog(context)
|
||||||
.then((String? value) async {
|
.then((String? value) async {
|
||||||
if (value != null) {
|
if (value != null) {
|
||||||
var datos = await Navigator.of(context).push(
|
var datos = await Navigator.of(context).push(
|
||||||
CupertinoPageRoute(
|
CupertinoPageRoute(
|
||||||
builder: (BuildContext context) {
|
builder: (_) =>
|
||||||
return CalendarProScreen(
|
CalendarProScreen(professional: pro),
|
||||||
professional: professionals[index],
|
|
||||||
);
|
|
||||||
},
|
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
|
|
||||||
if (datos != null) {
|
if (datos != null) {
|
||||||
datos.add(value);
|
datos.add(value);
|
||||||
Navigator.pop(context, datos);
|
Navigator.pop(context, datos);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
} else if (professionals[index].ubicacion ==
|
} else if (pro.ubicacion == 'sitio' ||
|
||||||
'sitio' ||
|
|
||||||
settings?.domicilios == false) {
|
settings?.domicilios == false) {
|
||||||
var datos = await Navigator.of(context).push(
|
var datos = await Navigator.of(context).push(
|
||||||
CupertinoPageRoute(
|
CupertinoPageRoute(
|
||||||
builder: (BuildContext context) {
|
builder: (_) =>
|
||||||
return CalendarProScreen(
|
CalendarProScreen(professional: pro),
|
||||||
professional: professionals[index],
|
|
||||||
);
|
|
||||||
},
|
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
|
|
||||||
if (datos != null) {
|
if (datos != null) {
|
||||||
datos.add('sitio');
|
datos.add('sitio');
|
||||||
Navigator.pop(context, datos);
|
Navigator.pop(context, datos);
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
Navigator.pop(
|
Navigator.pop(context, [pro, 'domicilio']);
|
||||||
context, [professionals[index], 'domicilio']);
|
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
);
|
);
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
import 'package:cloud_firestore/cloud_firestore.dart';
|
|
||||||
import 'package:flutter/foundation.dart';
|
import 'package:flutter/foundation.dart';
|
||||||
|
import 'package:prosappco/src/services/api_service.dart';
|
||||||
import 'package:flutter/gestures.dart';
|
import 'package:flutter/gestures.dart';
|
||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
import 'package:geocoding/geocoding.dart';
|
import 'package:geocoding/geocoding.dart';
|
||||||
@@ -89,23 +89,13 @@ class _ProfessionalDireccionScreenState
|
|||||||
Future<void> updateAddress(
|
Future<void> updateAddress(
|
||||||
String addressName, double latitude, double longitude) async {
|
String addressName, double latitude, double longitude) async {
|
||||||
try {
|
try {
|
||||||
await FirebaseFirestore.instance.collection('users').doc(uid).update({
|
await ApiService.instance.patch('/users/me', {
|
||||||
'address': addressName,
|
'address': addressName,
|
||||||
'latitude': latitude,
|
'latitude': latitude,
|
||||||
'longitude': longitude,
|
'longitude': longitude,
|
||||||
});
|
});
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
try {
|
print('Error al actualizar la dirección: $e');
|
||||||
await FirebaseFirestore.instance.collection('users').doc(uid).set({
|
|
||||||
'address': addressName,
|
|
||||||
'latitude': latitude,
|
|
||||||
'longitude': longitude,
|
|
||||||
});
|
|
||||||
} catch (e) {
|
|
||||||
print('Error al agregar la ciudad: $e');
|
|
||||||
}
|
|
||||||
|
|
||||||
print('Error al actualizar la ciudad: $e');
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
import 'package:cloud_firestore/cloud_firestore.dart';
|
|
||||||
import 'package:flutter/cupertino.dart';
|
import 'package:flutter/cupertino.dart';
|
||||||
|
import 'package:prosappco/src/services/api_service.dart';
|
||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
import 'package:flutter_rating_bar/flutter_rating_bar.dart';
|
import 'package:flutter_rating_bar/flutter_rating_bar.dart';
|
||||||
import 'package:intl/intl.dart';
|
import 'package:intl/intl.dart';
|
||||||
@@ -55,16 +55,14 @@ class _ProfessionalInfoScreenState extends State<ProfessionalInfoScreen> {
|
|||||||
}
|
}
|
||||||
|
|
||||||
void loadPaymentMethods() {
|
void loadPaymentMethods() {
|
||||||
FirebaseFirestore.instance
|
ApiService.instance.get('/professionals/${widget.professional.id}').then((data) {
|
||||||
.collection('users')
|
if (data is Map) {
|
||||||
.doc(widget.professional.id)
|
|
||||||
.get()
|
|
||||||
.then((doc) {
|
|
||||||
if (doc.exists) {
|
|
||||||
setState(() {
|
setState(() {
|
||||||
paymentMethods = Map<String, bool>.from(doc['paymentMethods'] ?? {});
|
paymentMethods = Map<String, bool>.from(data['paymentMethods'] ?? {});
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
}).catchError((e) {
|
||||||
|
print('Error loading paymentMethods: $e');
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -287,7 +285,7 @@ class _ProfessionalInfoScreenState extends State<ProfessionalInfoScreen> {
|
|||||||
left: 10,
|
left: 10,
|
||||||
),
|
),
|
||||||
child: ReferencePhoto(
|
child: ReferencePhoto(
|
||||||
ref: widget.professional.professionalRef,
|
ref: widget.professional.photoUrl,
|
||||||
size: 100,
|
size: 100,
|
||||||
sizeCircle: 100,
|
sizeCircle: 100,
|
||||||
sizeIcon: 50,
|
sizeIcon: 50,
|
||||||
|
|||||||
File diff suppressed because it is too large
Load Diff
@@ -1,17 +1,13 @@
|
|||||||
import 'package:cloud_firestore/cloud_firestore.dart';
|
|
||||||
import 'package:file_picker/file_picker.dart';
|
import 'package:file_picker/file_picker.dart';
|
||||||
import 'package:firebase_auth/firebase_auth.dart';
|
|
||||||
import 'package:firebase_storage/firebase_storage.dart';
|
|
||||||
import 'package:flutter/foundation.dart';
|
import 'package:flutter/foundation.dart';
|
||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
import 'package:flutter/services.dart';
|
import 'package:flutter/services.dart';
|
||||||
import 'package:get/get.dart';
|
import 'package:get/get.dart';
|
||||||
import 'package:image_picker/image_picker.dart';
|
import 'package:http/http.dart' as http;
|
||||||
import 'package:prosappco/src/authentication/authentication_repository.dart';
|
import 'package:prosappco/src/authentication/authentication_repository.dart';
|
||||||
import 'package:prosappco/src/components/photo_view_web.dart';
|
import 'package:prosappco/src/components/photo_view_web.dart';
|
||||||
import 'package:prosappco/src/components/pop_appbar.dart';
|
import 'package:prosappco/src/components/pop_appbar.dart';
|
||||||
import 'package:intl/intl.dart';
|
import 'package:prosappco/src/services/api_service.dart';
|
||||||
import 'dart:io';
|
|
||||||
|
|
||||||
class ProfessionalProfileWebScreen extends StatefulWidget {
|
class ProfessionalProfileWebScreen extends StatefulWidget {
|
||||||
const ProfessionalProfileWebScreen({super.key});
|
const ProfessionalProfileWebScreen({super.key});
|
||||||
@@ -24,62 +20,84 @@ class ProfessionalProfileWebScreen extends StatefulWidget {
|
|||||||
class _ProfessionalProfileWebScreenState
|
class _ProfessionalProfileWebScreenState
|
||||||
extends State<ProfessionalProfileWebScreen> {
|
extends State<ProfessionalProfileWebScreen> {
|
||||||
final uid = AuthenticationRepository.instance.getCurrentUserUid();
|
final uid = AuthenticationRepository.instance.getCurrentUserUid();
|
||||||
final FirebaseStorage storage = FirebaseStorage.instance;
|
|
||||||
|
|
||||||
// variables imagen
|
// file bytes (web)
|
||||||
|
Uint8List? selectedImagInBytes;
|
||||||
|
Uint8List? imageCedulaBytes;
|
||||||
|
Uint8List? imageCertificadoBytes;
|
||||||
|
List<Uint8List> imagesEspecializacionsBytes = [];
|
||||||
|
|
||||||
|
// file names for display
|
||||||
String selectedImage = '';
|
String selectedImage = '';
|
||||||
String selectedCedulaImage = '';
|
String selectedCedulaImage = '';
|
||||||
String selectedCertificadoImage = '';
|
String selectedCertificadoImage = '';
|
||||||
|
|
||||||
List selectedEspecializacionImages = [];
|
|
||||||
List<Uint8List> imagesEspecializacionsBytes = [];
|
|
||||||
|
|
||||||
XFile? file;
|
|
||||||
Uint8List? selectedImagInBytes;
|
|
||||||
XFile? image_cedula;
|
|
||||||
Uint8List? imageCedulaBytes;
|
|
||||||
XFile? image_certificado;
|
|
||||||
Uint8List? imageCertificadoBytes;
|
|
||||||
XFile? image_especializaciones;
|
|
||||||
Uint8List? imageEspecializacionesBytes;
|
|
||||||
|
|
||||||
// controllers
|
// controllers
|
||||||
final _formKey = GlobalKey<FormState>();
|
final _formKey = GlobalKey<FormState>();
|
||||||
final TextEditingController _cedulaController = TextEditingController();
|
final TextEditingController _cedulaController = TextEditingController();
|
||||||
final TextEditingController _especializacionController =
|
final TextEditingController _especializacionController =
|
||||||
TextEditingController();
|
TextEditingController();
|
||||||
|
|
||||||
// variables
|
|
||||||
bool _isLoading = false;
|
bool _isLoading = false;
|
||||||
String _profession = '...';
|
String _profession = '...';
|
||||||
String photoTemp = '';
|
String? _photoUrl;
|
||||||
String photoCedulaTemp = '';
|
|
||||||
String photoCertificadoTemp = '';
|
|
||||||
String _photo = '...';
|
|
||||||
|
|
||||||
@override
|
@override
|
||||||
void initState() {
|
void initState() {
|
||||||
super.initState();
|
super.initState();
|
||||||
if (_photo == '...') {
|
final currentUser = AuthenticationRepository.instance.currentUser.value;
|
||||||
AuthenticationRepository.instance.getPhoto(uid.toString()).then(
|
_photoUrl = currentUser?.picture;
|
||||||
(String s) => setState(() {
|
_profession = currentUser?.profession ?? '...';
|
||||||
_photo = s;
|
|
||||||
}),
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (_profession == '...') {
|
if (_photoUrl == null || _profession == '...') {
|
||||||
AuthenticationRepository.instance.getProfession(uid.toString()).then(
|
_loadUserData();
|
||||||
(String s) => setState(() {
|
|
||||||
_profession = s;
|
|
||||||
}),
|
|
||||||
);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
Future<void> _loadUserData() async {
|
||||||
|
try {
|
||||||
|
final Map<String, dynamic> data =
|
||||||
|
await ApiService.instance.get('/auth/me');
|
||||||
|
if (mounted) {
|
||||||
|
setState(() {
|
||||||
|
_photoUrl ??= data['picture'];
|
||||||
|
if (_profession == '...') {
|
||||||
|
_profession = data['profession'] ?? data['profesion'] ?? '...';
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
} catch (e) {
|
||||||
|
print('Error loading user data: $e');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Future<String?> _uploadBytes(Uint8List bytes, String filename) async {
|
||||||
|
try {
|
||||||
|
final uri = Uri.parse('${ApiService.baseUrl}/storage/upload');
|
||||||
|
final request = http.MultipartRequest('POST', uri);
|
||||||
|
final token = await ApiService.instance.getToken();
|
||||||
|
if (token != null) {
|
||||||
|
request.headers['Authorization'] = 'Bearer $token';
|
||||||
|
}
|
||||||
|
request.files.add(http.MultipartFile.fromBytes(
|
||||||
|
'file',
|
||||||
|
bytes,
|
||||||
|
filename: filename,
|
||||||
|
));
|
||||||
|
final streamed = await request.send();
|
||||||
|
final resp = await http.Response.fromStream(streamed);
|
||||||
|
if (resp.statusCode >= 200 && resp.statusCode < 300) {
|
||||||
|
final json = ApiService.instance.parseJson(resp.body);
|
||||||
|
return json['url'] as String?;
|
||||||
|
}
|
||||||
|
} catch (e) {
|
||||||
|
print('Error uploading bytes: $e');
|
||||||
|
}
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
_selectFile(bool imageFrom) async {
|
_selectFile(bool imageFrom) async {
|
||||||
FilePickerResult? fileResult = await FilePicker.platform.pickFiles();
|
FilePickerResult? fileResult = await FilePicker.platform.pickFiles();
|
||||||
|
|
||||||
if (fileResult != null) {
|
if (fileResult != null) {
|
||||||
setState(() {
|
setState(() {
|
||||||
selectedImage = fileResult.files.first.name;
|
selectedImage = fileResult.files.first.name;
|
||||||
@@ -90,7 +108,6 @@ class _ProfessionalProfileWebScreenState
|
|||||||
|
|
||||||
_selectFileCedula(bool imageFrom) async {
|
_selectFileCedula(bool imageFrom) async {
|
||||||
FilePickerResult? fileResult = await FilePicker.platform.pickFiles();
|
FilePickerResult? fileResult = await FilePicker.platform.pickFiles();
|
||||||
|
|
||||||
if (fileResult != null) {
|
if (fileResult != null) {
|
||||||
setState(() {
|
setState(() {
|
||||||
selectedCedulaImage = fileResult.files.first.name;
|
selectedCedulaImage = fileResult.files.first.name;
|
||||||
@@ -101,7 +118,6 @@ class _ProfessionalProfileWebScreenState
|
|||||||
|
|
||||||
_selectFileCertificado(bool imageFrom) async {
|
_selectFileCertificado(bool imageFrom) async {
|
||||||
FilePickerResult? fileResult = await FilePicker.platform.pickFiles();
|
FilePickerResult? fileResult = await FilePicker.platform.pickFiles();
|
||||||
|
|
||||||
if (fileResult != null) {
|
if (fileResult != null) {
|
||||||
setState(() {
|
setState(() {
|
||||||
selectedCertificadoImage = fileResult.files.first.name;
|
selectedCertificadoImage = fileResult.files.first.name;
|
||||||
@@ -115,18 +131,11 @@ class _ProfessionalProfileWebScreenState
|
|||||||
await FilePicker.platform.pickFiles(allowMultiple: true);
|
await FilePicker.platform.pickFiles(allowMultiple: true);
|
||||||
try {
|
try {
|
||||||
if (fileResult != null) {
|
if (fileResult != null) {
|
||||||
List<Uint8List> selectedFileBytes = [];
|
final List<Uint8List> selectedFileBytes = [];
|
||||||
|
|
||||||
for (var file in fileResult.files) {
|
for (var file in fileResult.files) {
|
||||||
Uint8List? bytes = file.bytes;
|
if (file.bytes != null) selectedFileBytes.add(file.bytes!);
|
||||||
if (bytes != null) {
|
|
||||||
selectedFileBytes.add(bytes);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
setState(() => imagesEspecializacionsBytes = selectedFileBytes);
|
||||||
setState(() {
|
|
||||||
imagesEspecializacionsBytes = selectedFileBytes;
|
|
||||||
});
|
|
||||||
}
|
}
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
print('$e');
|
print('$e');
|
||||||
@@ -134,9 +143,7 @@ class _ProfessionalProfileWebScreenState
|
|||||||
}
|
}
|
||||||
|
|
||||||
Future<void> sendInfo() async {
|
Future<void> sendInfo() async {
|
||||||
setState(() {
|
setState(() => _isLoading = true);
|
||||||
_isLoading = true;
|
|
||||||
});
|
|
||||||
|
|
||||||
final String cedula = _cedulaController.text.trim();
|
final String cedula = _cedulaController.text.trim();
|
||||||
final String especializacion = _especializacionController.text.trim();
|
final String especializacion = _especializacionController.text.trim();
|
||||||
@@ -145,40 +152,59 @@ class _ProfessionalProfileWebScreenState
|
|||||||
|
|
||||||
if (cedula.isEmpty) {
|
if (cedula.isEmpty) {
|
||||||
showSnackBar('Cedula invalida', 'Ingrese una cedula válida');
|
showSnackBar('Cedula invalida', 'Ingrese una cedula válida');
|
||||||
|
setState(() => _isLoading = false);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (imageCedulaBytes == null) {
|
if (imageCedulaBytes == null) {
|
||||||
showSnackBar('Cedula', 'Ingrese una imagen de su cedula');
|
showSnackBar('Cedula', 'Ingrese una imagen de su cedula');
|
||||||
|
setState(() => _isLoading = false);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (imageCertificadoBytes == null) {
|
if (imageCertificadoBytes == null) {
|
||||||
showSnackBar('Certificado', 'Ingrese una imagen de su certificado');
|
showSnackBar('Certificado', 'Ingrese una imagen de su certificado');
|
||||||
|
setState(() => _isLoading = false);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Actualiza los datos del usuario en Firestore
|
final Map<String, dynamic> body = {
|
||||||
await FirebaseFirestore.instance.collection('users').doc(uid).update({
|
|
||||||
'cedula': cedula,
|
'cedula': cedula,
|
||||||
'estado': 'revision',
|
'estado': 'revision',
|
||||||
'especializaciones': especializaciones
|
'especializaciones': especializaciones,
|
||||||
});
|
};
|
||||||
|
|
||||||
// Sube las imágenes al storage de Firebase
|
// Upload profile photo if selected
|
||||||
await uploadCedula();
|
if (selectedImagInBytes != null) {
|
||||||
await uploadCertificado();
|
final url =
|
||||||
List<String> uploadedPhotoPaths =
|
await _uploadBytes(selectedImagInBytes!, selectedImage.isNotEmpty ? selectedImage : 'profile.jpg');
|
||||||
await uploadEspecializaciones(imagesEspecializacionsBytes);
|
if (url != null) body['picture'] = url;
|
||||||
|
}
|
||||||
|
|
||||||
if (uploadedPhotoPaths.isNotEmpty) {
|
// Upload cedula
|
||||||
// Las imágenes se cargaron correctamente
|
final cedulaUrl = await _uploadBytes(imageCedulaBytes!,
|
||||||
// Actualiza las imágenes en Firestore
|
selectedCedulaImage.isNotEmpty ? selectedCedulaImage : 'cedula.pdf');
|
||||||
await updateFilesEspecializaciones(uploadedPhotoPaths);
|
if (cedulaUrl != null) body['imgCedula'] = cedulaUrl;
|
||||||
|
|
||||||
// Navega a la siguiente pantalla
|
// Upload certificado
|
||||||
|
final certUrl = await _uploadBytes(imageCertificadoBytes!,
|
||||||
|
selectedCertificadoImage.isNotEmpty ? selectedCertificadoImage : 'certificado.pdf');
|
||||||
|
if (certUrl != null) body['imgCertificado'] = certUrl;
|
||||||
|
|
||||||
|
// Upload especializaciones
|
||||||
|
if (imagesEspecializacionsBytes.isNotEmpty) {
|
||||||
|
final List<String> espUrls = [];
|
||||||
|
for (int i = 0; i < imagesEspecializacionsBytes.length; i++) {
|
||||||
|
final url = await _uploadBytes(
|
||||||
|
imagesEspecializacionsBytes[i], 'especializacion_$i.pdf');
|
||||||
|
if (url != null) espUrls.add(url);
|
||||||
|
}
|
||||||
|
if (espUrls.isNotEmpty) body['imgEspecializaciones'] = espUrls;
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
await ApiService.instance.patch('/users/me', body);
|
||||||
Navigator.pushReplacementNamed(context, '/solicitudEnviada');
|
Navigator.pushReplacementNamed(context, '/solicitudEnviada');
|
||||||
} else {
|
} catch (e) {
|
||||||
|
print('Error sending professional info: $e');
|
||||||
showDialog(
|
showDialog(
|
||||||
context: context,
|
context: context,
|
||||||
builder: (BuildContext context) {
|
builder: (BuildContext context) {
|
||||||
@@ -187,9 +213,7 @@ class _ProfessionalProfileWebScreenState
|
|||||||
content: const Text('Ocurrió un error al cargar las imágenes.'),
|
content: const Text('Ocurrió un error al cargar las imágenes.'),
|
||||||
actions: [
|
actions: [
|
||||||
TextButton(
|
TextButton(
|
||||||
onPressed: () {
|
onPressed: () => Navigator.pop(context),
|
||||||
Navigator.pop(context);
|
|
||||||
},
|
|
||||||
child: const Text('Aceptar'),
|
child: const Text('Aceptar'),
|
||||||
),
|
),
|
||||||
],
|
],
|
||||||
@@ -198,232 +222,22 @@ class _ProfessionalProfileWebScreenState
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Actualiza la imagen de perfil si hay cambios
|
setState(() => _isLoading = false);
|
||||||
if (selectedImagInBytes != null) {
|
|
||||||
await uploadFile();
|
|
||||||
await updateImage(photoTemp);
|
|
||||||
}
|
|
||||||
setState(() {
|
|
||||||
_isLoading = false;
|
|
||||||
});
|
|
||||||
|
|
||||||
// Navega a la siguiente pantalla
|
|
||||||
Navigator.pushReplacementNamed(context, '/solicitudEnviada');
|
|
||||||
}
|
|
||||||
|
|
||||||
uploadFile() async {
|
|
||||||
try {
|
|
||||||
final now = DateTime.now();
|
|
||||||
final formattedDate = DateFormat('HHmmssddMMyyyy').format(now);
|
|
||||||
final milliseconds = (now.microsecondsSinceEpoch / 1000).round();
|
|
||||||
final random = '$formattedDate$milliseconds';
|
|
||||||
|
|
||||||
final Reference ref = FirebaseStorage.instance
|
|
||||||
.ref()
|
|
||||||
.child('users')
|
|
||||||
.child(uid!)
|
|
||||||
.child('profile')
|
|
||||||
.child(random);
|
|
||||||
|
|
||||||
final metaData = SettableMetadata(contentType: 'image/jpeg');
|
|
||||||
|
|
||||||
final UploadTask uploadTask = ref.putData(selectedImagInBytes!, metaData);
|
|
||||||
|
|
||||||
final TaskSnapshot snapshot = await uploadTask.whenComplete(() => true);
|
|
||||||
|
|
||||||
photoTemp = ref.fullPath;
|
|
||||||
|
|
||||||
if (snapshot.state == TaskState.success) {
|
|
||||||
return true;
|
|
||||||
} else {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
} catch (e) {
|
|
||||||
print('web image error - $e');
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
uploadCedula() async {
|
|
||||||
try {
|
|
||||||
final now = DateTime.now();
|
|
||||||
final formattedDate = DateFormat('HHmmssddMMyyyy').format(now);
|
|
||||||
final milliseconds = (now.microsecondsSinceEpoch / 1000).round();
|
|
||||||
final random = 'c$formattedDate$milliseconds';
|
|
||||||
|
|
||||||
final Reference ref = FirebaseStorage.instance
|
|
||||||
.ref()
|
|
||||||
.child('users')
|
|
||||||
.child(uid!)
|
|
||||||
.child('cedula')
|
|
||||||
.child(random);
|
|
||||||
|
|
||||||
final metaData = SettableMetadata(contentType: 'application/pdf');
|
|
||||||
|
|
||||||
final UploadTask uploadTask = ref.putData(imageCedulaBytes!, metaData);
|
|
||||||
|
|
||||||
final TaskSnapshot snapshot = await uploadTask.whenComplete(() => true);
|
|
||||||
|
|
||||||
photoCedulaTemp = ref.fullPath;
|
|
||||||
|
|
||||||
if (snapshot.state == TaskState.success) {
|
|
||||||
updateImageCedula(photoCedulaTemp);
|
|
||||||
return true;
|
|
||||||
} else {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
} catch (e) {
|
|
||||||
print('web image cedula error - $e');
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
uploadCertificado() async {
|
|
||||||
try {
|
|
||||||
final now = DateTime.now();
|
|
||||||
final formattedDate = DateFormat('HHmmssddMMyyyy').format(now);
|
|
||||||
final milliseconds = (now.microsecondsSinceEpoch / 1000).round();
|
|
||||||
final random = 'f$formattedDate$milliseconds';
|
|
||||||
|
|
||||||
final Reference ref = FirebaseStorage.instance
|
|
||||||
.ref()
|
|
||||||
.child('users')
|
|
||||||
.child(uid!)
|
|
||||||
.child('certificado_profesional')
|
|
||||||
.child(random);
|
|
||||||
|
|
||||||
final metaData = SettableMetadata(contentType: 'application/pdf');
|
|
||||||
|
|
||||||
final UploadTask uploadTask =
|
|
||||||
ref.putData(imageCertificadoBytes!, metaData);
|
|
||||||
|
|
||||||
final TaskSnapshot snapshot = await uploadTask.whenComplete(() => true);
|
|
||||||
|
|
||||||
photoCertificadoTemp = ref.fullPath;
|
|
||||||
|
|
||||||
if (snapshot.state == TaskState.success) {
|
|
||||||
updateImageCertificado(photoCertificadoTemp);
|
|
||||||
return true;
|
|
||||||
} else {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
} catch (e) {
|
|
||||||
print('web image certificado error - $e');
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
Future<List<String>> uploadEspecializaciones(List<Uint8List> files) async {
|
|
||||||
List<String> filePaths = [];
|
|
||||||
|
|
||||||
for (Uint8List fileBytes in files) {
|
|
||||||
final Reference ref = FirebaseStorage.instance
|
|
||||||
.ref()
|
|
||||||
.child('users')
|
|
||||||
.child(uid!)
|
|
||||||
.child('especializaciones')
|
|
||||||
.child('e${DateTime.now().millisecondsSinceEpoch}.pdf');
|
|
||||||
|
|
||||||
final SettableMetadata metaData =
|
|
||||||
SettableMetadata(contentType: 'application/pdf');
|
|
||||||
|
|
||||||
final UploadTask uploadTask = ref.putData(fileBytes, metaData);
|
|
||||||
|
|
||||||
final TaskSnapshot snapshot = await uploadTask.whenComplete(() => true);
|
|
||||||
|
|
||||||
if (snapshot.state == TaskState.success) {
|
|
||||||
filePaths.add(ref.fullPath);
|
|
||||||
} else {
|
|
||||||
await updateFilesEspecializaciones(filePaths);
|
|
||||||
return [];
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
await updateFilesEspecializaciones(filePaths);
|
|
||||||
return filePaths;
|
|
||||||
}
|
|
||||||
|
|
||||||
Future<void> updateImageCedula(image) async {
|
|
||||||
try {
|
|
||||||
final userRef = FirebaseFirestore.instance.collection('users').doc(uid);
|
|
||||||
final userSnapshot = await userRef.get();
|
|
||||||
|
|
||||||
if (userSnapshot.exists) {
|
|
||||||
await userRef.update({'imgCedula': image});
|
|
||||||
} else {
|
|
||||||
await userRef.set({'imgCedula': image});
|
|
||||||
}
|
|
||||||
} catch (e) {
|
|
||||||
print('Error al agregar o actualizar la imagen de cédula: $e');
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
Future<void> updateImageCertificado(image) async {
|
|
||||||
try {
|
|
||||||
final userRef = FirebaseFirestore.instance.collection('users').doc(uid);
|
|
||||||
final userSnapshot = await userRef.get();
|
|
||||||
|
|
||||||
if (userSnapshot.exists) {
|
|
||||||
await userRef.update({'imgCertificado': image});
|
|
||||||
} else {
|
|
||||||
await userRef.set({'imgCertificado': image});
|
|
||||||
}
|
|
||||||
} catch (e) {
|
|
||||||
print('Error al agregar o actualizar la imagen de certificado: $e');
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
Future<void> updateImage(image) async {
|
|
||||||
try {
|
|
||||||
await FirebaseFirestore.instance
|
|
||||||
.collection('users')
|
|
||||||
.doc(uid)
|
|
||||||
.update({'photo': image});
|
|
||||||
} catch (e) {
|
|
||||||
try {
|
|
||||||
await FirebaseFirestore.instance
|
|
||||||
.collection('users')
|
|
||||||
.doc(uid)
|
|
||||||
.set({'photo': image});
|
|
||||||
} catch (e) {
|
|
||||||
print('Error al agregar la imagen de perfil: $e');
|
|
||||||
}
|
|
||||||
|
|
||||||
print('Error al actualizar la imagen de perfil: $e');
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
Future<void> updateFilesEspecializaciones(List<String> filePaths) async {
|
|
||||||
try {
|
|
||||||
final userRef = FirebaseFirestore.instance.collection('users').doc(uid);
|
|
||||||
final userSnapshot = await userRef.get();
|
|
||||||
|
|
||||||
if (userSnapshot.exists) {
|
|
||||||
await userRef.update({'imgEspecializaciones': filePaths});
|
|
||||||
} else {
|
|
||||||
await userRef.set({'imgEspecializaciones': filePaths});
|
|
||||||
}
|
|
||||||
} catch (e) {
|
|
||||||
print('Error al actualizar los archivos de especializaciones: $e');
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
void showSnackBar(String title, String message) {
|
void showSnackBar(String title, String message) {
|
||||||
Get.snackbar(
|
Get.snackbar(title, message, snackPosition: SnackPosition.TOP);
|
||||||
title,
|
|
||||||
message,
|
|
||||||
snackPosition: SnackPosition.TOP,
|
|
||||||
);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@override
|
@override
|
||||||
Widget build(BuildContext context) {
|
Widget build(BuildContext context) {
|
||||||
String profession = _profession.toString();
|
String profession = _profession.toString();
|
||||||
double _space = 10;
|
double space = 10;
|
||||||
|
|
||||||
return Scaffold(
|
return Scaffold(
|
||||||
backgroundColor: const Color(0xFFD6F4FF),
|
backgroundColor: const Color(0xFFD6F4FF),
|
||||||
appBar: PopAppbar(
|
appBar: PopAppbar(
|
||||||
onPressed: () {
|
onPressed: () => Navigator.pop(context),
|
||||||
Navigator.pop(context);
|
|
||||||
},
|
|
||||||
label: 'Perfil profesional'),
|
label: 'Perfil profesional'),
|
||||||
body: SingleChildScrollView(
|
body: SingleChildScrollView(
|
||||||
child: Center(
|
child: Center(
|
||||||
@@ -443,12 +257,14 @@ class _ProfessionalProfileWebScreenState
|
|||||||
)
|
)
|
||||||
: Column(
|
: Column(
|
||||||
children: [
|
children: [
|
||||||
Container(
|
GestureDetector(
|
||||||
padding: const EdgeInsets.only(top: 20),
|
onTap: () => _selectFile(true),
|
||||||
child: (selectedImagInBytes != null)
|
child: Padding(
|
||||||
? LocalPhotoWeb(file: selectedImagInBytes)
|
padding: const EdgeInsets.only(top: 20),
|
||||||
: ReferencePhotoWeb(
|
child: selectedImagInBytes != null
|
||||||
ref: storage.ref().child(_photo)),
|
? LocalPhotoWeb(file: selectedImagInBytes)
|
||||||
|
: ReferencePhotoWeb(ref: _photoUrl),
|
||||||
|
),
|
||||||
),
|
),
|
||||||
SizedBox(
|
SizedBox(
|
||||||
width: 300,
|
width: 300,
|
||||||
@@ -460,8 +276,7 @@ class _ProfessionalProfileWebScreenState
|
|||||||
keyboardType: TextInputType.number,
|
keyboardType: TextInputType.number,
|
||||||
controller: _cedulaController,
|
controller: _cedulaController,
|
||||||
inputFormatters: [
|
inputFormatters: [
|
||||||
FilteringTextInputFormatter
|
FilteringTextInputFormatter.digitsOnly,
|
||||||
.digitsOnly // Solo permite caracteres numéricos
|
|
||||||
],
|
],
|
||||||
validator: (String? value) {
|
validator: (String? value) {
|
||||||
if (value == null || value.isEmpty) {
|
if (value == null || value.isEmpty) {
|
||||||
@@ -474,15 +289,16 @@ class _ProfessionalProfileWebScreenState
|
|||||||
hintText: 'Cedula (Obligatorio)',
|
hintText: 'Cedula (Obligatorio)',
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
SizedBox(height: _space),
|
SizedBox(height: space),
|
||||||
ElevatedButton(
|
ElevatedButton(
|
||||||
onPressed: () {
|
onPressed: () =>
|
||||||
_selectFileCedula(true);
|
_selectFileCedula(true),
|
||||||
},
|
|
||||||
style: ElevatedButton.styleFrom(
|
style: ElevatedButton.styleFrom(
|
||||||
backgroundColor: const Color(0xFFD6F4FF),
|
backgroundColor:
|
||||||
|
const Color(0xFFD6F4FF),
|
||||||
shape: RoundedRectangleBorder(
|
shape: RoundedRectangleBorder(
|
||||||
borderRadius: BorderRadius.circular(50),
|
borderRadius:
|
||||||
|
BorderRadius.circular(50),
|
||||||
),
|
),
|
||||||
elevation: 0,
|
elevation: 0,
|
||||||
minimumSize: const Size(250, 50),
|
minimumSize: const Size(250, 50),
|
||||||
@@ -509,7 +325,7 @@ class _ProfessionalProfileWebScreenState
|
|||||||
],
|
],
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
SizedBox(height: _space),
|
SizedBox(height: space),
|
||||||
TextFormField(
|
TextFormField(
|
||||||
readOnly: true,
|
readOnly: true,
|
||||||
onTap: () async {
|
onTap: () async {
|
||||||
@@ -517,18 +333,16 @@ class _ProfessionalProfileWebScreenState
|
|||||||
(await Navigator.pushNamed(
|
(await Navigator.pushNamed(
|
||||||
context, '/profession'))
|
context, '/profession'))
|
||||||
as String?;
|
as String?;
|
||||||
|
|
||||||
if (profesion != null) {
|
if (profesion != null) {
|
||||||
setState(() {
|
setState(
|
||||||
_profession = profesion;
|
() => _profession = profesion);
|
||||||
});
|
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
decoration: InputDecoration(
|
decoration: InputDecoration(
|
||||||
prefixIcon: const Icon(
|
prefixIcon: const Icon(
|
||||||
Icons.assignment_ind_rounded),
|
Icons.assignment_ind_rounded),
|
||||||
suffixIcon:
|
suffixIcon: const Icon(
|
||||||
const Icon(Icons.arrow_drop_down),
|
Icons.arrow_drop_down),
|
||||||
hintStyle: profession == ''
|
hintStyle: profession == ''
|
||||||
? const TextStyle()
|
? const TextStyle()
|
||||||
: const TextStyle(
|
: const TextStyle(
|
||||||
@@ -537,15 +351,16 @@ class _ProfessionalProfileWebScreenState
|
|||||||
? 'Profesión (Obligatorio)'
|
? 'Profesión (Obligatorio)'
|
||||||
: profession),
|
: profession),
|
||||||
),
|
),
|
||||||
SizedBox(height: _space),
|
SizedBox(height: space),
|
||||||
ElevatedButton(
|
ElevatedButton(
|
||||||
onPressed: () {
|
onPressed: () =>
|
||||||
_selectFileCertificado(true);
|
_selectFileCertificado(true),
|
||||||
},
|
|
||||||
style: ElevatedButton.styleFrom(
|
style: ElevatedButton.styleFrom(
|
||||||
backgroundColor: const Color(0xFFD6F4FF),
|
backgroundColor:
|
||||||
|
const Color(0xFFD6F4FF),
|
||||||
shape: RoundedRectangleBorder(
|
shape: RoundedRectangleBorder(
|
||||||
borderRadius: BorderRadius.circular(50),
|
borderRadius:
|
||||||
|
BorderRadius.circular(50),
|
||||||
),
|
),
|
||||||
elevation: 0,
|
elevation: 0,
|
||||||
minimumSize: const Size(250, 50),
|
minimumSize: const Size(250, 50),
|
||||||
@@ -572,23 +387,24 @@ class _ProfessionalProfileWebScreenState
|
|||||||
],
|
],
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
SizedBox(height: _space),
|
SizedBox(height: space),
|
||||||
TextFormField(
|
TextFormField(
|
||||||
controller: _especializacionController,
|
controller: _especializacionController,
|
||||||
decoration: const InputDecoration(
|
decoration: const InputDecoration(
|
||||||
prefixIcon:
|
prefixIcon: Icon(
|
||||||
Icon(Icons.assignment_ind_rounded),
|
Icons.assignment_ind_rounded),
|
||||||
hintText: 'Especialización'),
|
hintText: 'Especialización'),
|
||||||
),
|
),
|
||||||
SizedBox(height: _space),
|
SizedBox(height: space),
|
||||||
ElevatedButton(
|
ElevatedButton(
|
||||||
onPressed: () async {
|
onPressed: () =>
|
||||||
_selectFilesEspecializaciones(true);
|
_selectFilesEspecializaciones(true),
|
||||||
},
|
|
||||||
style: ElevatedButton.styleFrom(
|
style: ElevatedButton.styleFrom(
|
||||||
backgroundColor: const Color(0xFFD6F4FF),
|
backgroundColor:
|
||||||
|
const Color(0xFFD6F4FF),
|
||||||
shape: RoundedRectangleBorder(
|
shape: RoundedRectangleBorder(
|
||||||
borderRadius: BorderRadius.circular(50),
|
borderRadius:
|
||||||
|
BorderRadius.circular(50),
|
||||||
),
|
),
|
||||||
elevation: 0,
|
elevation: 0,
|
||||||
minimumSize: const Size(250, 50),
|
minimumSize: const Size(250, 50),
|
||||||
@@ -656,7 +472,10 @@ class _ProfessionalProfileWebScreenState
|
|||||||
Container(
|
Container(
|
||||||
alignment: Alignment.bottomCenter,
|
alignment: Alignment.bottomCenter,
|
||||||
margin: const EdgeInsets.only(
|
margin: const EdgeInsets.only(
|
||||||
top: 30, right: 20, left: 20, bottom: 30),
|
top: 30,
|
||||||
|
right: 20,
|
||||||
|
left: 20,
|
||||||
|
bottom: 30),
|
||||||
child: ElevatedButton(
|
child: ElevatedButton(
|
||||||
onPressed: () {
|
onPressed: () {
|
||||||
if (_formKey.currentState!.validate()) {
|
if (_formKey.currentState!.validate()) {
|
||||||
|
|||||||
@@ -1,14 +1,13 @@
|
|||||||
import 'package:cloud_firestore/cloud_firestore.dart';
|
|
||||||
import 'package:firebase_auth/firebase_auth.dart';
|
|
||||||
import 'package:firebase_storage/firebase_storage.dart';
|
|
||||||
import 'package:flutter/cupertino.dart';
|
import 'package:flutter/cupertino.dart';
|
||||||
import 'package:flutter/foundation.dart';
|
import 'package:flutter/foundation.dart';
|
||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
import 'package:flutter/services.dart';
|
import 'package:flutter/services.dart';
|
||||||
import 'package:get/get.dart';
|
import 'package:get/get.dart';
|
||||||
import 'dart:io';
|
import 'dart:io';
|
||||||
|
import 'package:http/http.dart' as http;
|
||||||
import 'package:intl/intl.dart';
|
import 'package:intl/intl.dart';
|
||||||
import 'package:prosappco/src/authentication/authentication_repository.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';
|
import 'package:prosappco/src/components/pop_appbar.dart';
|
||||||
import 'package:prosappco/src/controllers/add_name_email_city.dart';
|
import 'package:prosappco/src/controllers/add_name_email_city.dart';
|
||||||
import 'package:prosappco/src/presentation/widgets/profile/birth_date_picker.dart';
|
import 'package:prosappco/src/presentation/widgets/profile/birth_date_picker.dart';
|
||||||
@@ -18,11 +17,11 @@ import 'package:prosappco/src/presentation/screens/new_password.dart';
|
|||||||
import 'package:prosappco/src/presentation/widgets/shared/primary_checkbox.dart';
|
import 'package:prosappco/src/presentation/widgets/shared/primary_checkbox.dart';
|
||||||
import 'package:prosappco/src/presentation/widgets/shared/warning_snackbar.dart';
|
import 'package:prosappco/src/presentation/widgets/shared/warning_snackbar.dart';
|
||||||
import 'package:prosappco/src/providers/user_provider.dart';
|
import 'package:prosappco/src/providers/user_provider.dart';
|
||||||
|
import 'package:prosappco/src/services/api_service.dart';
|
||||||
import 'package:prosappco/src/services/select_image_profile.dart';
|
import 'package:prosappco/src/services/select_image_profile.dart';
|
||||||
import 'package:prosappco/src/presentation/widgets/profile/gender_dropdown.dart';
|
import 'package:prosappco/src/presentation/widgets/profile/gender_dropdown.dart';
|
||||||
import 'package:prosappco/src/presentation/widgets/shared/primary_button.dart';
|
import 'package:prosappco/src/presentation/widgets/shared/primary_button.dart';
|
||||||
import 'package:provider/provider.dart';
|
import 'package:provider/provider.dart';
|
||||||
import '../../../components/photo_view.dart';
|
|
||||||
import 'package:universal_html/html.dart' as html;
|
import 'package:universal_html/html.dart' as html;
|
||||||
|
|
||||||
class ProfileScreen extends StatefulWidget {
|
class ProfileScreen extends StatefulWidget {
|
||||||
@@ -34,6 +33,7 @@ class ProfileScreen extends StatefulWidget {
|
|||||||
|
|
||||||
class _ProfileScreenState extends State<ProfileScreen> {
|
class _ProfileScreenState extends State<ProfileScreen> {
|
||||||
File? imagen_to_upload;
|
File? imagen_to_upload;
|
||||||
|
Uint8List? webImageBytes;
|
||||||
final DateFormat formatter = DateFormat('dd/MM/yyyy');
|
final DateFormat formatter = DateFormat('dd/MM/yyyy');
|
||||||
final uid = AuthenticationRepository.instance.getCurrentUserUid();
|
final uid = AuthenticationRepository.instance.getCurrentUserUid();
|
||||||
bool _obscureText = true;
|
bool _obscureText = true;
|
||||||
@@ -43,522 +43,108 @@ class _ProfileScreenState extends State<ProfileScreen> {
|
|||||||
final _nameController = TextEditingController();
|
final _nameController = TextEditingController();
|
||||||
final _emailController = TextEditingController();
|
final _emailController = TextEditingController();
|
||||||
final _passwordController = TextEditingController();
|
final _passwordController = TextEditingController();
|
||||||
late final FirebaseAuth _auth;
|
|
||||||
final FirebaseStorage storage = FirebaseStorage.instance;
|
|
||||||
var photoTemp = '';
|
|
||||||
var _ciudad = '...';
|
var _ciudad = '...';
|
||||||
var _photo = '..../images/perfil-2.png';
|
String? _photoUrl;
|
||||||
String? _email = '';
|
String? _email;
|
||||||
String gender = '';
|
String gender = '';
|
||||||
String genderDb = '';
|
String genderDb = '';
|
||||||
DateTime? birthDate;
|
DateTime? birthDate;
|
||||||
String birthDateDb = '';
|
String birthDateDb = '';
|
||||||
|
bool enableLoginWithEmail = false;
|
||||||
|
|
||||||
@override
|
@override
|
||||||
void initState() {
|
void initState() {
|
||||||
super.initState();
|
super.initState();
|
||||||
|
_loadCurrentUser();
|
||||||
_auth = FirebaseAuth.instance;
|
|
||||||
|
|
||||||
final currentUser = _auth.currentUser;
|
|
||||||
|
|
||||||
if (currentUser != null && currentUser.phoneNumber != null) {
|
|
||||||
_phoneNumberController.text = currentUser.phoneNumber!;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (currentUser != null && currentUser.displayName != null) {
|
|
||||||
_nameController.text = currentUser.displayName!;
|
|
||||||
}
|
|
||||||
if (currentUser != null && currentUser.email != null) {
|
|
||||||
_emailController.text = currentUser.email!;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (gender.isEmpty) {
|
|
||||||
AuthenticationRepository.instance.getGender(uid.toString()).then(
|
|
||||||
(String s) => setState(() {
|
|
||||||
genderDb = s;
|
|
||||||
}),
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (birthDate == null) {
|
|
||||||
AuthenticationRepository.instance.getBirthday(uid.toString()).then(
|
|
||||||
(String s) => setState(() {
|
|
||||||
if (s.isNotEmpty) {
|
|
||||||
birthDateDb = s;
|
|
||||||
}
|
|
||||||
}),
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
_email = currentUser?.email;
|
|
||||||
|
|
||||||
if (_ciudad == '...') {
|
|
||||||
AuthenticationRepository.instance.getCity(uid.toString()).then(
|
|
||||||
(String s) => setState(() {
|
|
||||||
_ciudad = s;
|
|
||||||
}),
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (_photo == '...') {
|
|
||||||
AuthenticationRepository.instance.getPhoto(uid.toString()).then(
|
|
||||||
(String s) => setState(() {
|
|
||||||
_photo = s;
|
|
||||||
}),
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
Future<void> updateImage(image) async {
|
Future<void> _loadCurrentUser() async {
|
||||||
try {
|
final currentUser = AuthenticationRepository.instance.currentUser.value;
|
||||||
await FirebaseFirestore.instance
|
if (currentUser != null) {
|
||||||
.collection('users')
|
_nameController.text = currentUser.name;
|
||||||
.doc(uid)
|
_emailController.text = currentUser.email ?? '';
|
||||||
.update({'photo': image});
|
_phoneNumberController.text = currentUser.phoneNumber ?? '';
|
||||||
} catch (e) {
|
_photoUrl = currentUser.picture;
|
||||||
try {
|
_ciudad = currentUser.city.isNotEmpty ? currentUser.city : '...';
|
||||||
await FirebaseFirestore.instance
|
_email = currentUser.email;
|
||||||
.collection('users')
|
genderDb = currentUser.gender ?? '';
|
||||||
.doc(uid)
|
birthDateDb = currentUser.birthday ?? '';
|
||||||
.set({'photo': image});
|
|
||||||
} catch (e) {
|
|
||||||
print('Error al agregar la imagen de perfil: $e');
|
|
||||||
}
|
|
||||||
|
|
||||||
print('Error al actualizar la imagen de perfil: $e');
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
Future<bool> uploadImage(File image) async {
|
|
||||||
final now = DateTime.now();
|
|
||||||
final formattedDate = DateFormat('HHmmssddMMyyyy').format(now);
|
|
||||||
final milliseconds = (now.microsecondsSinceEpoch / 1000).round();
|
|
||||||
final random = '$formattedDate$milliseconds';
|
|
||||||
|
|
||||||
Reference ref =
|
|
||||||
storage.ref().child('users').child(uid!).child('profile').child(random);
|
|
||||||
|
|
||||||
final UploadTask uploadTask = ref.putFile(image);
|
|
||||||
|
|
||||||
final TaskSnapshot snapshot = await uploadTask.whenComplete(() => true);
|
|
||||||
|
|
||||||
photoTemp = ref.fullPath;
|
|
||||||
|
|
||||||
if (snapshot.state == TaskState.success) {
|
|
||||||
return true;
|
|
||||||
} else {
|
} else {
|
||||||
return false;
|
// Fallback: fetch from API
|
||||||
|
try {
|
||||||
|
final Map<String, dynamic> data =
|
||||||
|
await ApiService.instance.get('/auth/me');
|
||||||
|
if (mounted) {
|
||||||
|
setState(() {
|
||||||
|
_nameController.text = data['name'] ?? '';
|
||||||
|
_emailController.text = data['email'] ?? '';
|
||||||
|
_phoneNumberController.text = data['phone'] ?? '';
|
||||||
|
_photoUrl = data['picture'];
|
||||||
|
_ciudad = data['city'] ?? '...';
|
||||||
|
_email = data['email'];
|
||||||
|
genderDb = data['gender'] ?? '';
|
||||||
|
birthDateDb = data['birthday'] ?? '';
|
||||||
|
});
|
||||||
|
}
|
||||||
|
} catch (e) {
|
||||||
|
print('Error loading user: $e');
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
Future<Widget> downloadImage(Reference ref) async {
|
Future<String?> _uploadImageFile(File image) async {
|
||||||
try {
|
try {
|
||||||
if (_photo == '...' || _photo.isEmpty) {
|
final uri =
|
||||||
return GestureDetector(
|
Uri.parse('${ApiService.baseUrl}/storage/upload');
|
||||||
onTap: () {
|
final request = http.MultipartRequest('POST', uri);
|
||||||
_showChoiceDialog(context);
|
final token = await ApiService.instance.getToken();
|
||||||
},
|
if (token != null) {
|
||||||
child: Container(
|
request.headers['Authorization'] = 'Bearer $token';
|
||||||
margin: const EdgeInsets.symmetric(vertical: 50),
|
}
|
||||||
width: 100,
|
request.files.add(await http.MultipartFile.fromPath('file', image.path));
|
||||||
height: 100,
|
final streamed = await request.send();
|
||||||
decoration: BoxDecoration(
|
final resp = await http.Response.fromStream(streamed);
|
||||||
color: const Color(0xFF2BA4EC),
|
if (resp.statusCode >= 200 && resp.statusCode < 300) {
|
||||||
borderRadius: BorderRadius.circular(50),
|
final json = ApiService.instance.parseJson(resp.body);
|
||||||
),
|
return json['url'] as String?;
|
||||||
child: const Icon(
|
|
||||||
Icons.person,
|
|
||||||
color: Colors.white,
|
|
||||||
size: 90,
|
|
||||||
),
|
|
||||||
),
|
|
||||||
);
|
|
||||||
} else {
|
|
||||||
final imageData = await ref.getData();
|
|
||||||
if (imageData != null) {
|
|
||||||
final widgetImage = GestureDetector(
|
|
||||||
onTap: () {
|
|
||||||
_showChoiceDialog(context);
|
|
||||||
},
|
|
||||||
child: Container(
|
|
||||||
margin: const EdgeInsets.symmetric(vertical: 50),
|
|
||||||
child: ClipOval(
|
|
||||||
child: Image.memory(
|
|
||||||
imageData,
|
|
||||||
width: 60,
|
|
||||||
height: 60,
|
|
||||||
fit: BoxFit.cover,
|
|
||||||
),
|
|
||||||
),
|
|
||||||
),
|
|
||||||
);
|
|
||||||
return widgetImage;
|
|
||||||
} else {
|
|
||||||
return GestureDetector(
|
|
||||||
onTap: () {
|
|
||||||
_showChoiceDialog(context);
|
|
||||||
},
|
|
||||||
child: Container(
|
|
||||||
margin: const EdgeInsets.symmetric(vertical: 50),
|
|
||||||
width: 100,
|
|
||||||
height: 100,
|
|
||||||
decoration: BoxDecoration(
|
|
||||||
color: const Color(0xFF2BA4EC),
|
|
||||||
borderRadius: BorderRadius.circular(50),
|
|
||||||
),
|
|
||||||
child: const Icon(
|
|
||||||
Icons.person,
|
|
||||||
color: Colors.white,
|
|
||||||
size: 90,
|
|
||||||
),
|
|
||||||
),
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
return GestureDetector(
|
print('Error uploading image: $e');
|
||||||
onTap: () {
|
|
||||||
_showChoiceDialog(context);
|
|
||||||
},
|
|
||||||
child: Container(
|
|
||||||
margin: const EdgeInsets.symmetric(vertical: 50),
|
|
||||||
width: 100,
|
|
||||||
height: 100,
|
|
||||||
decoration: BoxDecoration(
|
|
||||||
color: const Color(0xFF2BA4EC),
|
|
||||||
borderRadius: BorderRadius.circular(50),
|
|
||||||
),
|
|
||||||
child: const Icon(
|
|
||||||
Icons.person,
|
|
||||||
color: Colors.white,
|
|
||||||
size: 90,
|
|
||||||
),
|
|
||||||
),
|
|
||||||
);
|
|
||||||
}
|
}
|
||||||
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
Future<void> updateInfo() async {
|
Future<void> updateInfo() async {
|
||||||
final currentUser = _auth.currentUser;
|
final Map<String, dynamic> body = {};
|
||||||
final currentPhoneNumber = _auth.currentUser!.phoneNumber;
|
|
||||||
|
|
||||||
String newName = _nameController.text.trim();
|
final newName = _nameController.text.trim();
|
||||||
String newEmail = _emailController.text.trim();
|
if (newName.isNotEmpty) body['name'] = newName;
|
||||||
String newPassword = _passwordController.text.trim();
|
if (_ciudad != '...' && _ciudad.isNotEmpty) body['city'] = _ciudad;
|
||||||
|
if (gender.isNotEmpty) body['gender'] = gender;
|
||||||
if (gender.isNotEmpty) {
|
if (birthDate != null) body['birthday'] = birthDate.toString();
|
||||||
try {
|
|
||||||
await FirebaseFirestore.instance
|
|
||||||
.collection('users')
|
|
||||||
.doc(uid)
|
|
||||||
.set({'gender': gender}, SetOptions(merge: true));
|
|
||||||
|
|
||||||
genderDb = gender;
|
|
||||||
} catch (e) {
|
|
||||||
print('Error al actualizar el genero: $e');
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if (birthDate != null) {
|
|
||||||
try {
|
|
||||||
await FirebaseFirestore.instance
|
|
||||||
.collection('users')
|
|
||||||
.doc(uid)
|
|
||||||
.set({'birth_date': birthDate.toString()}, SetOptions(merge: true));
|
|
||||||
|
|
||||||
birthDateDb = birthDate.toString();
|
|
||||||
} catch (e) {
|
|
||||||
print('Error al actualizar la fecha de nacimiento: $e');
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
setState(() {});
|
|
||||||
|
|
||||||
if (newName.isEmpty) {
|
|
||||||
Get.snackbar(
|
|
||||||
'Nombre Invalido',
|
|
||||||
'Ingresa un nombre válido.',
|
|
||||||
snackPosition: SnackPosition.BOTTOM,
|
|
||||||
);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (enableLoginWithEmail) {
|
|
||||||
if (newEmail.isEmpty) {
|
|
||||||
Get.snackbar(
|
|
||||||
'Correo Invalido',
|
|
||||||
'Ingresa un email válido.',
|
|
||||||
snackPosition: SnackPosition.BOTTOM,
|
|
||||||
);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if (currentUser?.displayName != newName) {
|
|
||||||
try {
|
|
||||||
await FirebaseAuth.instance.currentUser!.updateDisplayName(newName);
|
|
||||||
await FirebaseFirestore.instance
|
|
||||||
.collection('users')
|
|
||||||
.doc(uid)
|
|
||||||
.update({'name': newName, 'lowerName': newName.toLowerCase()});
|
|
||||||
} catch (e) {
|
|
||||||
print('Error al actualizar el nombre: $e');
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
String? selectedCity = _ciudad;
|
|
||||||
|
|
||||||
if (kIsWeb) {
|
|
||||||
try {
|
|
||||||
await FirebaseFirestore.instance
|
|
||||||
.collection('users')
|
|
||||||
.doc(uid)
|
|
||||||
.update({'city': selectedCity});
|
|
||||||
} catch (e) {
|
|
||||||
print('Error al actualizar la ciudad: $e');
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if (enableLoginWithEmail) {
|
|
||||||
if (currentUser?.email != newEmail) {
|
|
||||||
if (newPassword.isNotEmpty) {
|
|
||||||
bool updateEmailSuccess =
|
|
||||||
await updateEmailAndPassword(newEmail, newPassword);
|
|
||||||
|
|
||||||
if (updateEmailSuccess) {
|
|
||||||
await FirebaseFirestore.instance
|
|
||||||
.collection('users')
|
|
||||||
.doc(uid)
|
|
||||||
.update({'email': newEmail});
|
|
||||||
} else {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
Get.snackbar(
|
|
||||||
'Contraseña Invalida',
|
|
||||||
'Por favor ingresa una contraseña.',
|
|
||||||
snackPosition: SnackPosition.BOTTOM,
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
try {
|
try {
|
||||||
await FirebaseFirestore.instance
|
if (imagen_to_upload != null) {
|
||||||
.collection('users')
|
final url = await _uploadImageFile(imagen_to_upload!);
|
||||||
.doc(uid)
|
if (url != null) body['picture'] = url;
|
||||||
.update({'phoneNumber': currentPhoneNumber});
|
|
||||||
} catch (e) {
|
|
||||||
print('$e');
|
|
||||||
}
|
|
||||||
|
|
||||||
try {
|
|
||||||
if (imagen_to_upload == null) {
|
|
||||||
} else {
|
|
||||||
final uploaded = await uploadImage(imagen_to_upload!);
|
|
||||||
updateImage(photoTemp);
|
|
||||||
}
|
}
|
||||||
} catch (e) {
|
|
||||||
print('Error al actualizar la imagen de perfil $e');
|
|
||||||
}
|
|
||||||
|
|
||||||
WarningSnackbar.show(
|
await ApiService.instance.patch('/users/me', body);
|
||||||
title: 'Informacion actualizada',
|
|
||||||
message: 'Tu informacion ha sido actualizada con exito.',
|
|
||||||
icon: const Icon(
|
|
||||||
Icons.check,
|
|
||||||
color: Colors.white,
|
|
||||||
),
|
|
||||||
backgroundColor: Colors.green,
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
Future<void> _updateEmailAndPassword(
|
|
||||||
String newEmail, String currentPassword) async {
|
|
||||||
final user = _auth.currentUser;
|
|
||||||
|
|
||||||
if (user!.email! == newEmail) {
|
|
||||||
Get.snackbar(
|
|
||||||
'No se puede actualizar',
|
|
||||||
'El correo actual no puede ser actualizado.',
|
|
||||||
snackPosition: SnackPosition.BOTTOM,
|
|
||||||
);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!newEmail.contains('@') || !newEmail.contains('.')) {
|
|
||||||
Get.snackbar(
|
|
||||||
'No se puede actualizar',
|
|
||||||
'Ingresa un correo electrónico valido.',
|
|
||||||
snackPosition: SnackPosition.BOTTOM,
|
|
||||||
);
|
|
||||||
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
final emailExistsQuery = await FirebaseFirestore.instance
|
|
||||||
.collection('users')
|
|
||||||
.where('email', isEqualTo: newEmail)
|
|
||||||
.get();
|
|
||||||
|
|
||||||
if (emailExistsQuery.docs.isNotEmpty) {
|
|
||||||
Get.snackbar(
|
|
||||||
'No se puede actualizar',
|
|
||||||
'El nuevo correo electrónico ya está en uso.',
|
|
||||||
snackPosition: SnackPosition.BOTTOM,
|
|
||||||
);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
try {
|
|
||||||
final credential = EmailAuthProvider.credential(
|
|
||||||
email: user.email!, password: currentPassword);
|
|
||||||
await user.reauthenticateWithCredential(credential);
|
|
||||||
|
|
||||||
await user.updateEmail(newEmail);
|
|
||||||
|
|
||||||
await FirebaseFirestore.instance
|
|
||||||
.collection('users')
|
|
||||||
.doc(uid)
|
|
||||||
.update({'email': newEmail});
|
|
||||||
|
|
||||||
setState(() {
|
|
||||||
_email = newEmail;
|
|
||||||
});
|
|
||||||
|
|
||||||
WarningSnackbar.show(
|
WarningSnackbar.show(
|
||||||
title: 'Actualizado exitosamente',
|
title: 'Informacion actualizada',
|
||||||
message: 'Correo electronico actualizado correctamente.',
|
message: 'Tu informacion ha sido actualizada con exito.',
|
||||||
icon: const Icon(Icons.check, color: Colors.white),
|
icon: const Icon(Icons.check, color: Colors.white),
|
||||||
backgroundColor: Colors.green,
|
backgroundColor: Colors.green,
|
||||||
);
|
);
|
||||||
|
|
||||||
if (Navigator.canPop(context)) {
|
|
||||||
Navigator.of(context).pop();
|
|
||||||
}
|
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
WarningSnackbar.show(
|
print('Error updating info: $e');
|
||||||
title: 'No se pudo actualizar el correo',
|
Get.snackbar('Error', 'No se pudo actualizar la información.',
|
||||||
message:
|
snackPosition: SnackPosition.BOTTOM);
|
||||||
'Verifica tu contraseña actual y asegúrate de que el nuevo correo electrónico no se haya utilizado previamente.',
|
|
||||||
);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
Future<void> _showEmailUpdateDialog(BuildContext context) async {
|
|
||||||
TextEditingController emailController = TextEditingController();
|
|
||||||
TextEditingController passwordController = TextEditingController();
|
|
||||||
|
|
||||||
showDialog(
|
|
||||||
context: context,
|
|
||||||
builder: (BuildContext context) {
|
|
||||||
return AlertDialog(
|
|
||||||
title: const Text('Actualizar Email'),
|
|
||||||
content: Column(
|
|
||||||
mainAxisSize: MainAxisSize.min,
|
|
||||||
children: [
|
|
||||||
TextField(
|
|
||||||
controller: emailController,
|
|
||||||
decoration: const InputDecoration(labelText: 'Nuevo Email'),
|
|
||||||
),
|
|
||||||
TextField(
|
|
||||||
controller: passwordController,
|
|
||||||
decoration:
|
|
||||||
const InputDecoration(labelText: 'Contraseña Actual'),
|
|
||||||
obscureText: true,
|
|
||||||
),
|
|
||||||
],
|
|
||||||
),
|
|
||||||
actions: [
|
|
||||||
TextButton(
|
|
||||||
onPressed: () {
|
|
||||||
Navigator.of(context).pop();
|
|
||||||
},
|
|
||||||
child: const Text(
|
|
||||||
'Cancelar',
|
|
||||||
style: TextStyle(color: Colors.grey),
|
|
||||||
),
|
|
||||||
),
|
|
||||||
TextButton(
|
|
||||||
onPressed: () {
|
|
||||||
String newEmail = emailController.text.trim();
|
|
||||||
String currentPassword = passwordController.text.trim();
|
|
||||||
if (newEmail.isNotEmpty && currentPassword.isNotEmpty) {
|
|
||||||
_updateEmailAndPassword(newEmail, currentPassword);
|
|
||||||
}
|
|
||||||
},
|
|
||||||
child: const Text(
|
|
||||||
'Guardar',
|
|
||||||
style: TextStyle(
|
|
||||||
color: Colors.blue,
|
|
||||||
fontWeight: FontWeight.w600,
|
|
||||||
),
|
|
||||||
),
|
|
||||||
),
|
|
||||||
],
|
|
||||||
);
|
|
||||||
},
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
Future<bool> updateEmailAndPassword(String email, String password) async {
|
|
||||||
final User? user = FirebaseAuth.instance.currentUser;
|
|
||||||
if (user != null) {
|
|
||||||
try {
|
|
||||||
await user.updateEmail(email);
|
|
||||||
await user.updatePassword(password);
|
|
||||||
|
|
||||||
return true;
|
|
||||||
} catch (e) {
|
|
||||||
WarningSnackbar.show(
|
|
||||||
title: 'Inicia sesión de nuevo',
|
|
||||||
message: 'Inicia la sesión de nuevo para guardar los cambios.',
|
|
||||||
);
|
|
||||||
AuthenticationRepository.instance.logout(uid!);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
bool enableLoginWithEmail = false;
|
|
||||||
|
|
||||||
Future<List<City>> _getCities() async {
|
|
||||||
List<City> citys = [];
|
|
||||||
if (kIsWeb) {
|
|
||||||
try {
|
|
||||||
QuerySnapshot countries = await countriesCollection.get();
|
|
||||||
for (DocumentSnapshot country in countries.docs) {
|
|
||||||
String countryName = country.id;
|
|
||||||
Map<String, dynamic> data = country.data() as Map<String, dynamic>;
|
|
||||||
Map<String, Map<String, String>> states = {};
|
|
||||||
|
|
||||||
for (var entry in data.entries) {
|
|
||||||
String key = entry.key;
|
|
||||||
Map<String, String> cityData =
|
|
||||||
Map<String, String>.from(entry.value);
|
|
||||||
states[key] = cityData;
|
|
||||||
}
|
|
||||||
|
|
||||||
for (var state in states.entries) {
|
|
||||||
var citysState = state.value.entries.map((city) => City(
|
|
||||||
cityName: city.key,
|
|
||||||
coordsOfCity: city.value,
|
|
||||||
stateOfCity: state.key,
|
|
||||||
countryOfCity: countryName,
|
|
||||||
));
|
|
||||||
|
|
||||||
citys.addAll(citysState);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
} catch (e) {
|
|
||||||
print('Error obteniendo las ciudades: $e');
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return citys;
|
|
||||||
}
|
|
||||||
|
|
||||||
Future<void> _showChoiceDialog(BuildContext context) async {
|
Future<void> _showChoiceDialog(BuildContext context) async {
|
||||||
return showDialog(
|
return showDialog(
|
||||||
context: context,
|
context: context,
|
||||||
@@ -607,14 +193,11 @@ class _ProfileScreenState extends State<ProfileScreen> {
|
|||||||
@override
|
@override
|
||||||
Widget build(BuildContext context) {
|
Widget build(BuildContext context) {
|
||||||
String city = _ciudad.toString();
|
String city = _ciudad.toString();
|
||||||
|
|
||||||
final userProvider = Provider.of<UserProvider>(context);
|
final userProvider = Provider.of<UserProvider>(context);
|
||||||
|
|
||||||
return Scaffold(
|
return Scaffold(
|
||||||
appBar: PopAppbar(
|
appBar: PopAppbar(
|
||||||
onPressed: () {
|
onPressed: () => Navigator.pop(context),
|
||||||
Navigator.pop(context);
|
|
||||||
},
|
|
||||||
label: 'Perfil',
|
label: 'Perfil',
|
||||||
),
|
),
|
||||||
body: SingleChildScrollView(
|
body: SingleChildScrollView(
|
||||||
@@ -623,14 +206,13 @@ class _ProfileScreenState extends State<ProfileScreen> {
|
|||||||
child: Column(
|
child: Column(
|
||||||
children: [
|
children: [
|
||||||
GestureDetector(
|
GestureDetector(
|
||||||
onTap: () {
|
onTap: () => _showChoiceDialog(context),
|
||||||
_showChoiceDialog(context);
|
|
||||||
},
|
|
||||||
child: Container(
|
child: Container(
|
||||||
margin: const EdgeInsets.symmetric(vertical: 20),
|
margin: const EdgeInsets.symmetric(vertical: 20),
|
||||||
child: (imagen_to_upload != null)
|
child: imagen_to_upload != null
|
||||||
? LocalPhoto(file: imagen_to_upload!)
|
? LocalPhoto(file: imagen_to_upload!)
|
||||||
: ReferencePhoto(ref: storage.ref().child(_photo))),
|
: ReferencePhoto(ref: _photoUrl),
|
||||||
|
),
|
||||||
),
|
),
|
||||||
Container(
|
Container(
|
||||||
width: 300,
|
width: 300,
|
||||||
@@ -660,71 +242,35 @@ class _ProfileScreenState extends State<ProfileScreen> {
|
|||||||
),
|
),
|
||||||
),
|
),
|
||||||
const SizedBox(),
|
const SizedBox(),
|
||||||
kIsWeb
|
TextFormField(
|
||||||
? FutureBuilder<List<City>>(
|
readOnly: true,
|
||||||
future: _getCities(),
|
onTap: () async {
|
||||||
builder: (context, snapshot) {
|
final String? ciudad = await Navigator.push(
|
||||||
if (snapshot.connectionState ==
|
context,
|
||||||
ConnectionState.waiting) {
|
CupertinoPageRoute(
|
||||||
return const Center(
|
builder: (BuildContext context) {
|
||||||
child: CircularProgressIndicator(),
|
return const CityScreen();
|
||||||
);
|
|
||||||
} else if (snapshot.hasError) {
|
|
||||||
return const Center(
|
|
||||||
child:
|
|
||||||
Text('Error al obtener las ciudades'),
|
|
||||||
);
|
|
||||||
} else {
|
|
||||||
List<City> filteredCities = snapshot.data!;
|
|
||||||
|
|
||||||
return DropdownButtonFormField<String>(
|
|
||||||
value: _ciudad,
|
|
||||||
onChanged: (String? newValue) {
|
|
||||||
setState(() {
|
|
||||||
_ciudad = newValue!;
|
|
||||||
});
|
|
||||||
},
|
|
||||||
items: filteredCities.map((City city) {
|
|
||||||
return DropdownMenuItem<String>(
|
|
||||||
value: city.cityName,
|
|
||||||
child: Text(city.cityName ?? ''),
|
|
||||||
);
|
|
||||||
}).toList(),
|
|
||||||
decoration: InputDecoration(
|
|
||||||
prefixIcon: const Icon(Icons.near_me),
|
|
||||||
hintText: _ciudad,
|
|
||||||
),
|
|
||||||
);
|
|
||||||
}
|
|
||||||
},
|
},
|
||||||
)
|
|
||||||
: TextFormField(
|
|
||||||
readOnly: true,
|
|
||||||
onTap: () async {
|
|
||||||
final String? ciudad = await Navigator.push(
|
|
||||||
context,
|
|
||||||
CupertinoPageRoute(
|
|
||||||
builder: (BuildContext context) {
|
|
||||||
return const CityScreen();
|
|
||||||
},
|
|
||||||
),
|
|
||||||
) as String?;
|
|
||||||
|
|
||||||
if (ciudad != null) {
|
|
||||||
setState(() {
|
|
||||||
_ciudad = ciudad;
|
|
||||||
});
|
|
||||||
}
|
|
||||||
},
|
|
||||||
decoration: InputDecoration(
|
|
||||||
prefixIcon: const Icon(Icons.near_me),
|
|
||||||
suffixIcon: const Icon(Icons.arrow_drop_down),
|
|
||||||
hintStyle: city == ''
|
|
||||||
? const TextStyle()
|
|
||||||
: const TextStyle(color: Colors.black87),
|
|
||||||
hintText: city == '' ? 'Ciudad' : city,
|
|
||||||
),
|
|
||||||
),
|
),
|
||||||
|
) as String?;
|
||||||
|
|
||||||
|
if (ciudad != null) {
|
||||||
|
setState(() {
|
||||||
|
_ciudad = ciudad;
|
||||||
|
});
|
||||||
|
}
|
||||||
|
},
|
||||||
|
decoration: InputDecoration(
|
||||||
|
prefixIcon: const Icon(Icons.near_me),
|
||||||
|
suffixIcon: const Icon(Icons.arrow_drop_down),
|
||||||
|
hintStyle: city == ''
|
||||||
|
? const TextStyle()
|
||||||
|
: const TextStyle(color: Colors.black87),
|
||||||
|
hintText: city == '' || city == '...'
|
||||||
|
? 'Ciudad'
|
||||||
|
: city,
|
||||||
|
),
|
||||||
|
),
|
||||||
const SizedBox(height: 20.0),
|
const SizedBox(height: 20.0),
|
||||||
TextFormField(
|
TextFormField(
|
||||||
controller: _phoneNumberController,
|
controller: _phoneNumberController,
|
||||||
@@ -778,152 +324,15 @@ class _ProfileScreenState extends State<ProfileScreen> {
|
|||||||
: const SizedBox(),
|
: const SizedBox(),
|
||||||
_email != null && _email != ''
|
_email != null && _email != ''
|
||||||
? TextFormField(
|
? TextFormField(
|
||||||
onTap: () {
|
|
||||||
_showEmailUpdateDialog(context);
|
|
||||||
},
|
|
||||||
readOnly: true,
|
readOnly: true,
|
||||||
controller: _emailController,
|
controller: _emailController,
|
||||||
decoration: const InputDecoration(
|
decoration: const InputDecoration(
|
||||||
prefixIcon: Icon(Icons.email_outlined),
|
prefixIcon: Icon(Icons.email_outlined),
|
||||||
hintText: 'Email (Obligatorio)',
|
hintText: 'Email',
|
||||||
),
|
),
|
||||||
)
|
)
|
||||||
: const SizedBox(),
|
: const SizedBox(),
|
||||||
const SizedBox(height: 20),
|
const SizedBox(height: 20),
|
||||||
_email == null || _email == ''
|
|
||||||
? PrimaryCheckbox(
|
|
||||||
text:
|
|
||||||
'Habilitar inicio de sesión con correo (Opcional)',
|
|
||||||
initialValue: enableLoginWithEmail,
|
|
||||||
onChanged: (value) {
|
|
||||||
setState(() {
|
|
||||||
enableLoginWithEmail = value;
|
|
||||||
});
|
|
||||||
},
|
|
||||||
)
|
|
||||||
: const SizedBox(),
|
|
||||||
const SizedBox(height: 15),
|
|
||||||
enableLoginWithEmail
|
|
||||||
? Container(
|
|
||||||
decoration: BoxDecoration(
|
|
||||||
border: Border.all(
|
|
||||||
color: Colors.blue,
|
|
||||||
width: 0.5,
|
|
||||||
),
|
|
||||||
borderRadius: BorderRadius.circular(10),
|
|
||||||
),
|
|
||||||
padding: const EdgeInsets.all(10),
|
|
||||||
child: Column(
|
|
||||||
children: [
|
|
||||||
TextFormField(
|
|
||||||
controller: _emailController,
|
|
||||||
validator: (String? value) {
|
|
||||||
if (enableLoginWithEmail) {
|
|
||||||
if (value == null || value.isEmpty) {
|
|
||||||
return 'Por favor ingrese un email';
|
|
||||||
}
|
|
||||||
final RegExp emailRegExp = RegExp(
|
|
||||||
r'^[\w-\.]+@([\w-]+\.)+[\w-]{2,4}$');
|
|
||||||
if (!emailRegExp.hasMatch(value)) {
|
|
||||||
return 'Por favor ingrese un email válido';
|
|
||||||
}
|
|
||||||
return null;
|
|
||||||
} else {
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
},
|
|
||||||
decoration: const InputDecoration(
|
|
||||||
prefixIcon: Icon(Icons.email_outlined),
|
|
||||||
hintText: 'Email',
|
|
||||||
),
|
|
||||||
),
|
|
||||||
const SizedBox(height: 20.0),
|
|
||||||
_email != null && _email != ''
|
|
||||||
? const SizedBox.shrink()
|
|
||||||
: TextFormField(
|
|
||||||
controller: _passwordController,
|
|
||||||
obscureText: _obscureText,
|
|
||||||
validator: (value) {
|
|
||||||
if (enableLoginWithEmail) {
|
|
||||||
if (value == null ||
|
|
||||||
value.isEmpty) {
|
|
||||||
return 'Por favor ingrese una contraseña.';
|
|
||||||
}
|
|
||||||
if (value.length < 5) {
|
|
||||||
return 'Debe tener al menos 5 caracteres.';
|
|
||||||
}
|
|
||||||
return null;
|
|
||||||
} else {
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
},
|
|
||||||
decoration: InputDecoration(
|
|
||||||
prefixIcon: const Icon(
|
|
||||||
Icons.lock_outline),
|
|
||||||
suffixIcon: IconButton(
|
|
||||||
icon: Icon(
|
|
||||||
_obscureText
|
|
||||||
? Icons.visibility
|
|
||||||
: Icons.visibility_off,
|
|
||||||
color: Colors.grey,
|
|
||||||
),
|
|
||||||
onPressed: () {
|
|
||||||
setState(() {
|
|
||||||
_obscureText =
|
|
||||||
!_obscureText;
|
|
||||||
});
|
|
||||||
},
|
|
||||||
),
|
|
||||||
hintText: 'Contraseña'),
|
|
||||||
),
|
|
||||||
_email != null && _email != ''
|
|
||||||
? const SizedBox.shrink()
|
|
||||||
: const SizedBox(height: 20),
|
|
||||||
Container(
|
|
||||||
margin: const EdgeInsets.only(
|
|
||||||
left: 5,
|
|
||||||
right: 5,
|
|
||||||
top: 5,
|
|
||||||
bottom: 5,
|
|
||||||
),
|
|
||||||
padding: const EdgeInsets.symmetric(
|
|
||||||
horizontal: 10, vertical: 8),
|
|
||||||
decoration: BoxDecoration(
|
|
||||||
color: const Color(0xFFD6F4FF),
|
|
||||||
borderRadius: BorderRadius.circular(20),
|
|
||||||
boxShadow: [
|
|
||||||
BoxShadow(
|
|
||||||
color: Colors.grey.withOpacity(0.5),
|
|
||||||
spreadRadius: 1,
|
|
||||||
blurRadius: 5,
|
|
||||||
offset: const Offset(1, 3),
|
|
||||||
),
|
|
||||||
],
|
|
||||||
),
|
|
||||||
child: const Row(
|
|
||||||
children: [
|
|
||||||
Icon(
|
|
||||||
Icons.error_outline,
|
|
||||||
size: 20,
|
|
||||||
color: Colors.black54,
|
|
||||||
),
|
|
||||||
SizedBox(width: 10),
|
|
||||||
Expanded(
|
|
||||||
child: Text(
|
|
||||||
'Al habilitar el inicio de sesión con correo, se cerrara la sesión actual.',
|
|
||||||
style: TextStyle(
|
|
||||||
color: Colors.black54,
|
|
||||||
fontSize: 13,
|
|
||||||
),
|
|
||||||
),
|
|
||||||
),
|
|
||||||
],
|
|
||||||
),
|
|
||||||
)
|
|
||||||
],
|
|
||||||
),
|
|
||||||
)
|
|
||||||
: const SizedBox(),
|
|
||||||
],
|
],
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
@@ -955,12 +364,9 @@ class _ProfileScreenState extends State<ProfileScreen> {
|
|||||||
PrimaryButton(
|
PrimaryButton(
|
||||||
onPressed: () async {
|
onPressed: () async {
|
||||||
if (_formKey.currentState!.validate()) {
|
if (_formKey.currentState!.validate()) {
|
||||||
|
await updateInfo();
|
||||||
if (kIsWeb) {
|
if (kIsWeb) {
|
||||||
await updateInfo().whenComplete(() {
|
html.window.location.reload();
|
||||||
html.window.location.reload();
|
|
||||||
});
|
|
||||||
} else {
|
|
||||||
await updateInfo();
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
await userProvider.updateUserDataAndScores();
|
await userProvider.updateUserDataAndScores();
|
||||||
|
|||||||
@@ -1,21 +1,20 @@
|
|||||||
import 'dart:io';
|
import 'dart:io';
|
||||||
import 'package:cloud_firestore/cloud_firestore.dart';
|
|
||||||
import 'package:firebase_storage/firebase_storage.dart';
|
|
||||||
import 'package:flutter/cupertino.dart';
|
import 'package:flutter/cupertino.dart';
|
||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
import 'package:flutter/services.dart';
|
import 'package:flutter/services.dart';
|
||||||
import 'package:flutter_animate/flutter_animate.dart';
|
import 'package:flutter_animate/flutter_animate.dart';
|
||||||
import 'package:get/get.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/authentication/authentication_repository.dart';
|
||||||
import 'package:prosappco/src/components/banner_photo.dart';
|
import 'package:prosappco/src/components/banner_photo.dart';
|
||||||
import 'package:prosappco/src/components/pop_appbar.dart';
|
import 'package:prosappco/src/components/pop_appbar.dart';
|
||||||
import 'package:prosappco/src/components/schedule_picker.dart';
|
import 'package:prosappco/src/components/schedule_picker.dart';
|
||||||
import 'package:prosappco/src/models/setting_model.dart';
|
import 'package:prosappco/src/models/setting_model.dart';
|
||||||
import 'package:prosappco/src/presentation/screens/horario.dart';
|
import 'package:prosappco/src/presentation/screens/horario.dart';
|
||||||
import 'package:prosappco/src/presentation/screens/professional.dart';
|
|
||||||
import 'package:prosappco/src/presentation/screens/professional_direccion.dart';
|
import 'package:prosappco/src/presentation/screens/professional_direccion.dart';
|
||||||
import 'package:prosappco/src/presentation/widgets/shared/primary_button.dart';
|
import 'package:prosappco/src/presentation/widgets/shared/primary_button.dart';
|
||||||
import 'package:prosappco/src/presentation/widgets/shared/primary_checkbox.dart';
|
import 'package:prosappco/src/presentation/widgets/shared/primary_checkbox.dart';
|
||||||
|
import 'package:prosappco/src/services/api_service.dart';
|
||||||
import 'package:prosappco/src/services/select_image_profile.dart';
|
import 'package:prosappco/src/services/select_image_profile.dart';
|
||||||
|
|
||||||
class ProfileProScreen extends StatefulWidget {
|
class ProfileProScreen extends StatefulWidget {
|
||||||
@@ -35,184 +34,116 @@ class _ProfileProScreenState extends State<ProfileProScreen> {
|
|||||||
File? imagen_to_upload;
|
File? imagen_to_upload;
|
||||||
|
|
||||||
bool tarifaValue = false;
|
bool tarifaValue = false;
|
||||||
|
|
||||||
bool domicilioValue = false;
|
bool domicilioValue = false;
|
||||||
bool sitioValue = false;
|
bool sitioValue = false;
|
||||||
|
|
||||||
var photoTemp = '';
|
String? _bannerUrl;
|
||||||
|
|
||||||
var _photo = '...';
|
|
||||||
var _direccion = '...';
|
var _direccion = '...';
|
||||||
var _ubicacion = '...';
|
var _ubicacion = '...';
|
||||||
var _opcionalAddress = '...';
|
|
||||||
int _tarifa = 0;
|
int _tarifa = 0;
|
||||||
SettingModel? settings;
|
SettingModel? settings;
|
||||||
|
|
||||||
bool nequiValue = false;
|
|
||||||
bool banktransferValue = false;
|
|
||||||
bool datafoneValue = false;
|
|
||||||
|
|
||||||
Map<String, Schedule>? _horarios;
|
|
||||||
|
|
||||||
Map<String, bool> paymentMethods = {
|
Map<String, bool> paymentMethods = {
|
||||||
'Nequi': false,
|
'Nequi': false,
|
||||||
'Transferencia Bancaria': false,
|
'Transferencia Bancaria': false,
|
||||||
'Datafono': false,
|
'Datafono': false,
|
||||||
};
|
};
|
||||||
|
|
||||||
|
Map<String, Schedule>? _horarios;
|
||||||
|
|
||||||
@override
|
@override
|
||||||
void initState() {
|
void initState() {
|
||||||
super.initState();
|
super.initState();
|
||||||
if (settings == null) {
|
SettingModel.getSettings()
|
||||||
SettingModel.getSettings().then((SettingModel value) => setState(
|
.then((v) => setState(() => settings = v));
|
||||||
() => settings = value,
|
Schedule.getHorarios(uid.toString())
|
||||||
));
|
.then((data) => setState(() => _horarios = data));
|
||||||
}
|
_loadProData();
|
||||||
|
}
|
||||||
|
|
||||||
final uid = AuthenticationRepository.instance.getCurrentUserUid();
|
Future<void> _loadProData() async {
|
||||||
|
try {
|
||||||
|
final Map<String, dynamic> data =
|
||||||
|
await ApiService.instance.get('/auth/me');
|
||||||
|
final pro = data['professionals'] as Map<String, dynamic>?;
|
||||||
|
|
||||||
if (_photo == '...') {
|
if (mounted) {
|
||||||
AuthenticationRepository.instance.getBanner(uid.toString()).then(
|
setState(() {
|
||||||
(String s) => setState(
|
_bannerUrl = pro?['banner_picture'];
|
||||||
() {
|
_direccion = data['address'] ?? '...';
|
||||||
_photo = s;
|
_ubicacion = pro?['ubicacion'] ?? data['ubicacion'] ?? '...';
|
||||||
},
|
_tarifa = pro?['rate'] != null
|
||||||
),
|
? (double.tryParse(pro!['rate'].toString())?.toInt() ?? 0)
|
||||||
);
|
: 0;
|
||||||
}
|
|
||||||
|
|
||||||
if (_horarios == null) {
|
if (_tarifa != 0) {
|
||||||
Schedule.getHorarios(uid.toString()).then(
|
tarifaValue = true;
|
||||||
(Map<String, Schedule> data) {
|
_tarifaController.text = _tarifa.toString();
|
||||||
setState(() {
|
}
|
||||||
_horarios = data;
|
|
||||||
});
|
|
||||||
},
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (_direccion == '...') {
|
if (_ubicacion == 'ambos') {
|
||||||
AuthenticationRepository.instance
|
sitioValue = true;
|
||||||
.getAddress(uid.toString())
|
domicilioValue = true;
|
||||||
.then((String s) => setState(() {
|
} else if (_ubicacion == 'sitio') {
|
||||||
_direccion = s;
|
sitioValue = true;
|
||||||
}));
|
} else if (_ubicacion == 'domicilio') {
|
||||||
}
|
domicilioValue = true;
|
||||||
if (_ubicacion == '...') {
|
}
|
||||||
AuthenticationRepository.instance.getUbicacion(uid.toString()).then(
|
|
||||||
(String s) => setState(
|
|
||||||
() {
|
|
||||||
_ubicacion = s;
|
|
||||||
if (_ubicacion == 'ambos') {
|
|
||||||
sitioValue = true;
|
|
||||||
domicilioValue = true;
|
|
||||||
} else if (_ubicacion == 'sitio') {
|
|
||||||
sitioValue = true;
|
|
||||||
} else if (_ubicacion == 'domicilio') {
|
|
||||||
domicilioValue = true;
|
|
||||||
}
|
|
||||||
},
|
|
||||||
),
|
|
||||||
);
|
|
||||||
}
|
|
||||||
if (_opcionalAddress == '...') {
|
|
||||||
AuthenticationRepository.instance.getOpcionalAddress(uid.toString()).then(
|
|
||||||
(String s) => setState(
|
|
||||||
() {
|
|
||||||
_opcionalAddress = s;
|
|
||||||
|
|
||||||
if (_opcionalAddress != '...') {
|
final pm = pro?['paymentMethods'];
|
||||||
_opcionalAddressController.text = _opcionalAddress;
|
if (pm is Map) {
|
||||||
}
|
paymentMethods = Map<String, bool>.from(pm);
|
||||||
},
|
}
|
||||||
),
|
|
||||||
);
|
|
||||||
}
|
|
||||||
if (_tarifa == 0) {
|
|
||||||
AuthenticationRepository.instance.getTarifa(uid.toString()).then(
|
|
||||||
(s) => setState(
|
|
||||||
() {
|
|
||||||
_tarifa = s;
|
|
||||||
|
|
||||||
if (_tarifa != 0) {
|
final opcAddr = pro?['opcional_address'] ?? data['opcional_address'];
|
||||||
tarifaValue = true;
|
if (opcAddr != null && opcAddr.toString().isNotEmpty) {
|
||||||
_tarifaController.text = _tarifa.toString();
|
_opcionalAddressController.text = opcAddr.toString();
|
||||||
}
|
}
|
||||||
},
|
});
|
||||||
),
|
}
|
||||||
);
|
} catch (e) {
|
||||||
|
print('Error loading pro data: $e');
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
loadPaymentMethods();
|
Future<String?> _uploadImageFile(File image) async {
|
||||||
|
try {
|
||||||
|
final uri = Uri.parse('${ApiService.baseUrl}/storage/upload');
|
||||||
|
final request = http.MultipartRequest('POST', uri);
|
||||||
|
final token = await ApiService.instance.getToken();
|
||||||
|
if (token != null) {
|
||||||
|
request.headers['Authorization'] = 'Bearer $token';
|
||||||
|
}
|
||||||
|
request.files.add(await http.MultipartFile.fromPath('file', image.path));
|
||||||
|
final streamed = await request.send();
|
||||||
|
final resp = await http.Response.fromStream(streamed);
|
||||||
|
if (resp.statusCode >= 200 && resp.statusCode < 300) {
|
||||||
|
final json = ApiService.instance.parseJson(resp.body);
|
||||||
|
return json['url'] as String?;
|
||||||
|
}
|
||||||
|
} catch (e) {
|
||||||
|
print('Error uploading banner: $e');
|
||||||
|
}
|
||||||
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
void createSchedules() async {
|
void createSchedules() async {
|
||||||
if (_horarios == null || _horarios!.isEmpty) {
|
if (_horarios == null || _horarios!.isEmpty) {
|
||||||
final defaultSchedule = {
|
// Initialize default schedules via API
|
||||||
'1': {
|
final Map<String, dynamic> defaultSchedule = {};
|
||||||
|
for (var i = 1; i <= 7; i++) {
|
||||||
|
defaultSchedule['$i'] = {
|
||||||
'habilitado': false,
|
'habilitado': false,
|
||||||
'jornadaContinua': false,
|
'jornadaContinua': false,
|
||||||
'range1Hour1': null,
|
'range1Hour1': null,
|
||||||
'range1Hour2': null,
|
'range1Hour2': null,
|
||||||
'range2Hour1': null,
|
'range2Hour1': null,
|
||||||
'range2Hour2': null
|
'range2Hour2': null,
|
||||||
},
|
};
|
||||||
'2': {
|
}
|
||||||
'habilitado': false,
|
|
||||||
'jornadaContinua': false,
|
|
||||||
'range1Hour1': null,
|
|
||||||
'range1Hour2': null,
|
|
||||||
'range2Hour1': null,
|
|
||||||
'range2Hour2': null
|
|
||||||
},
|
|
||||||
'3': {
|
|
||||||
'habilitado': false,
|
|
||||||
'jornadaContinua': false,
|
|
||||||
'range1Hour1': null,
|
|
||||||
'range1Hour2': null,
|
|
||||||
'range2Hour1': null,
|
|
||||||
'range2Hour2': null
|
|
||||||
},
|
|
||||||
'4': {
|
|
||||||
'habilitado': false,
|
|
||||||
'jornadaContinua': false,
|
|
||||||
'range1Hour1': null,
|
|
||||||
'range1Hour2': null,
|
|
||||||
'range2Hour1': null,
|
|
||||||
'range2Hour2': null
|
|
||||||
},
|
|
||||||
'5': {
|
|
||||||
'habilitado': false,
|
|
||||||
'jornadaContinua': false,
|
|
||||||
'range1Hour1': null,
|
|
||||||
'range1Hour2': null,
|
|
||||||
'range2Hour1': null,
|
|
||||||
'range2Hour2': null
|
|
||||||
},
|
|
||||||
'6': {
|
|
||||||
'habilitado': false,
|
|
||||||
'jornadaContinua': false,
|
|
||||||
'range1Hour1': null,
|
|
||||||
'range1Hour2': null,
|
|
||||||
'range2Hour1': null,
|
|
||||||
'range2Hour2': null
|
|
||||||
},
|
|
||||||
'7': {
|
|
||||||
'habilitado': false,
|
|
||||||
'jornadaContinua': false,
|
|
||||||
'range1Hour1': null,
|
|
||||||
'range1Hour2': null,
|
|
||||||
'range2Hour1': null,
|
|
||||||
'range2Hour2': null
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
try {
|
try {
|
||||||
await FirebaseFirestore.instance
|
await ApiService.instance
|
||||||
.collection('users')
|
.patch('/users/me', {'horario': defaultSchedule});
|
||||||
.doc(uid)
|
|
||||||
.update({'horario': defaultSchedule});
|
|
||||||
|
|
||||||
Navigator.pop(context);
|
Navigator.pop(context);
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
print(e);
|
print(e);
|
||||||
@@ -222,9 +153,7 @@ class _ProfileProScreenState extends State<ProfileProScreen> {
|
|||||||
context,
|
context,
|
||||||
CupertinoPageRoute(
|
CupertinoPageRoute(
|
||||||
builder: (BuildContext context) {
|
builder: (BuildContext context) {
|
||||||
return HorarioScreen(
|
return HorarioScreen(horarios: _horarios!);
|
||||||
horarios: _horarios!,
|
|
||||||
);
|
|
||||||
},
|
},
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
@@ -232,30 +161,19 @@ class _ProfileProScreenState extends State<ProfileProScreen> {
|
|||||||
}
|
}
|
||||||
|
|
||||||
Future<void> updateInfo() async {
|
Future<void> updateInfo() async {
|
||||||
if (_opcionalAddressController.text.isNotEmpty) {
|
final Map<String, dynamic> body = {};
|
||||||
FirebaseFirestore.instance
|
|
||||||
.collection('users')
|
|
||||||
.doc(uid)
|
|
||||||
.update({'opcional_address': _opcionalAddressController.text});
|
|
||||||
}
|
|
||||||
if (tarifaValue && _tarifaController.text.isNotEmpty) {
|
|
||||||
FirebaseFirestore.instance
|
|
||||||
.collection('users')
|
|
||||||
.doc(uid)
|
|
||||||
.update({'tarifa': int.parse(_tarifaController.text)});
|
|
||||||
} else {
|
|
||||||
FirebaseFirestore.instance
|
|
||||||
.collection('users')
|
|
||||||
.doc(uid)
|
|
||||||
.update({'tarifa': 0});
|
|
||||||
}
|
|
||||||
|
|
||||||
|
if (_opcionalAddressController.text.isNotEmpty) {
|
||||||
|
body['opcional_address'] = _opcionalAddressController.text;
|
||||||
|
}
|
||||||
|
body['rate'] = tarifaValue && _tarifaController.text.isNotEmpty
|
||||||
|
? int.parse(_tarifaController.text)
|
||||||
|
: 0;
|
||||||
|
|
||||||
|
String? ubicacion;
|
||||||
if (settings?.domicilios == false) {
|
if (settings?.domicilios == false) {
|
||||||
if (sitioValue) {
|
if (sitioValue) {
|
||||||
FirebaseFirestore.instance
|
ubicacion = 'sitio';
|
||||||
.collection('users')
|
|
||||||
.doc(uid)
|
|
||||||
.update({'ubicacion': 'sitio'});
|
|
||||||
} else {
|
} else {
|
||||||
Get.snackbar(
|
Get.snackbar(
|
||||||
'Elige como vas a dar tu servicio',
|
'Elige como vas a dar tu servicio',
|
||||||
@@ -267,29 +185,15 @@ class _ProfileProScreenState extends State<ProfileProScreen> {
|
|||||||
style: TextStyle(color: Colors.white),
|
style: TextStyle(color: Colors.white),
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
|
|
||||||
FirebaseFirestore.instance
|
|
||||||
.collection('users')
|
|
||||||
.doc(uid)
|
|
||||||
.update({'ubicacion': null});
|
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
if (domicilioValue && sitioValue) {
|
if (domicilioValue && sitioValue) {
|
||||||
FirebaseFirestore.instance
|
ubicacion = 'ambos';
|
||||||
.collection('users')
|
|
||||||
.doc(uid)
|
|
||||||
.update({'ubicacion': 'ambos'});
|
|
||||||
} else if (domicilioValue) {
|
} else if (domicilioValue) {
|
||||||
FirebaseFirestore.instance
|
ubicacion = 'domicilio';
|
||||||
.collection('users')
|
|
||||||
.doc(uid)
|
|
||||||
.update({'ubicacion': 'domicilio'});
|
|
||||||
} else if (sitioValue) {
|
} else if (sitioValue) {
|
||||||
FirebaseFirestore.instance
|
ubicacion = 'sitio';
|
||||||
.collection('users')
|
|
||||||
.doc(uid)
|
|
||||||
.update({'ubicacion': 'sitio'});
|
|
||||||
} else {
|
} else {
|
||||||
Get.snackbar(
|
Get.snackbar(
|
||||||
'Elige como vas a dar tu servicio',
|
'Elige como vas a dar tu servicio',
|
||||||
@@ -304,18 +208,19 @@ class _ProfileProScreenState extends State<ProfileProScreen> {
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
body['ubicacion'] = ubicacion;
|
||||||
|
body['paymentMethods'] = paymentMethods;
|
||||||
|
|
||||||
try {
|
if (imagen_to_upload != null) {
|
||||||
if (imagen_to_upload == null) {
|
final url = await _uploadImageFile(imagen_to_upload!);
|
||||||
} else {
|
if (url != null) body['banner_picture'] = url;
|
||||||
updateImage(photoTemp);
|
|
||||||
//image
|
|
||||||
}
|
|
||||||
} catch (e) {
|
|
||||||
print('Error al actualizar la imagen de perfil $e');
|
|
||||||
}
|
}
|
||||||
|
|
||||||
updatePaymentMethods();
|
try {
|
||||||
|
await ApiService.instance.patch('/users/me', body);
|
||||||
|
} catch (e) {
|
||||||
|
print('Error updating pro info: $e');
|
||||||
|
}
|
||||||
|
|
||||||
if (settings?.domicilios == false && sitioValue == false) {
|
if (settings?.domicilios == false && sitioValue == false) {
|
||||||
Get.defaultDialog(
|
Get.defaultDialog(
|
||||||
@@ -324,9 +229,7 @@ class _ProfileProScreenState extends State<ProfileProScreen> {
|
|||||||
'Si no eliges servicio en sitio, no serás visible para los usuarios.',
|
'Si no eliges servicio en sitio, no serás visible para los usuarios.',
|
||||||
actions: [
|
actions: [
|
||||||
ElevatedButton(
|
ElevatedButton(
|
||||||
onPressed: () {
|
onPressed: () => Get.back(),
|
||||||
Get.back();
|
|
||||||
},
|
|
||||||
child: const Text('Entendido'),
|
child: const Text('Entendido'),
|
||||||
),
|
),
|
||||||
],
|
],
|
||||||
@@ -339,51 +242,6 @@ class _ProfileProScreenState extends State<ProfileProScreen> {
|
|||||||
);
|
);
|
||||||
Navigator.pop(context);
|
Navigator.pop(context);
|
||||||
}
|
}
|
||||||
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
Future<void> updateImage(image) async {
|
|
||||||
try {
|
|
||||||
await FirebaseFirestore.instance
|
|
||||||
.collection('users')
|
|
||||||
.doc(uid)
|
|
||||||
.update({'banner': image});
|
|
||||||
} catch (e) {
|
|
||||||
try {
|
|
||||||
await FirebaseFirestore.instance
|
|
||||||
.collection('users')
|
|
||||||
.doc(uid)
|
|
||||||
.set({'banner': image});
|
|
||||||
} catch (e) {
|
|
||||||
print('Error al agregar la imagen de perfil: $e');
|
|
||||||
}
|
|
||||||
|
|
||||||
print('Error al actualizar la imagen de perfil: $e');
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
Future<bool> uploadImage(File image) async {
|
|
||||||
final String namefile = image.path.split('/').last;
|
|
||||||
|
|
||||||
Reference ref = storage
|
|
||||||
.ref()
|
|
||||||
.child('users')
|
|
||||||
.child(uid!)
|
|
||||||
.child('profile')
|
|
||||||
.child(namefile);
|
|
||||||
|
|
||||||
final UploadTask uploadTask = ref.putFile(image);
|
|
||||||
|
|
||||||
final TaskSnapshot snapshot = await uploadTask.whenComplete(() => true);
|
|
||||||
|
|
||||||
photoTemp = ref.fullPath;
|
|
||||||
|
|
||||||
if (snapshot.state == TaskState.success) {
|
|
||||||
return true;
|
|
||||||
} else {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
Future<void> _showChoiceDialog(BuildContext context) async {
|
Future<void> _showChoiceDialog(BuildContext context) async {
|
||||||
@@ -434,34 +292,14 @@ class _ProfileProScreenState extends State<ProfileProScreen> {
|
|||||||
void toggleDomicilio(bool newValue) {
|
void toggleDomicilio(bool newValue) {
|
||||||
setState(() {
|
setState(() {
|
||||||
domicilioValue = newValue;
|
domicilioValue = newValue;
|
||||||
if (newValue == false && sitioValue == false) {
|
if (newValue == false && sitioValue == false) sitioValue = true;
|
||||||
sitioValue = true;
|
|
||||||
}
|
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
void toggleSitio(bool newValue) {
|
void toggleSitio(bool newValue) {
|
||||||
setState(() {
|
setState(() {
|
||||||
sitioValue = newValue;
|
sitioValue = newValue;
|
||||||
if (newValue == false && domicilioValue == false) {
|
if (newValue == false && domicilioValue == false) domicilioValue = true;
|
||||||
domicilioValue = true;
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
void updatePaymentMethods() {
|
|
||||||
FirebaseFirestore.instance.collection('users').doc(uid).update({
|
|
||||||
'paymentMethods': paymentMethods,
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
void loadPaymentMethods() {
|
|
||||||
FirebaseFirestore.instance.collection('users').doc(uid).get().then((doc) {
|
|
||||||
if (doc.exists) {
|
|
||||||
setState(() {
|
|
||||||
paymentMethods = Map<String, bool>.from(doc['paymentMethods'] ?? {});
|
|
||||||
});
|
|
||||||
}
|
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -471,41 +309,24 @@ class _ProfileProScreenState extends State<ProfileProScreen> {
|
|||||||
|
|
||||||
return Scaffold(
|
return Scaffold(
|
||||||
appBar: PopAppbar(
|
appBar: PopAppbar(
|
||||||
onPressed: () {
|
onPressed: () => Navigator.pop(context),
|
||||||
Navigator.pop(context);
|
|
||||||
},
|
|
||||||
label: 'Perfil profesional'),
|
label: 'Perfil profesional'),
|
||||||
body: SingleChildScrollView(
|
body: SingleChildScrollView(
|
||||||
reverse: true,
|
reverse: true,
|
||||||
child: Column(
|
child: Column(
|
||||||
children: [
|
children: [
|
||||||
GestureDetector(
|
GestureDetector(
|
||||||
onTap: () {
|
onTap: () => _showChoiceDialog(context),
|
||||||
_showChoiceDialog(context);
|
child: imagen_to_upload != null
|
||||||
},
|
? LocalPhoto(file: imagen_to_upload!)
|
||||||
child: Container(
|
: ReferenceBannerPhoto(ref: _bannerUrl),
|
||||||
child: (imagen_to_upload != null)
|
|
||||||
? LocalPhoto(
|
|
||||||
file: imagen_to_upload!,
|
|
||||||
)
|
|
||||||
: ReferenceBannerPhoto(
|
|
||||||
ref: storage.ref().child(_photo),
|
|
||||||
),
|
|
||||||
),
|
|
||||||
),
|
),
|
||||||
if (settings?.tarifas == true)
|
if (settings?.tarifas == true)
|
||||||
const Divider(
|
const Divider(color: Colors.white, height: 12),
|
||||||
color: Colors.white,
|
|
||||||
height: 12,
|
|
||||||
),
|
|
||||||
if (settings?.tarifas == true)
|
if (settings?.tarifas == true)
|
||||||
customSwitch(
|
customSwitch('Tarifa', tarifaValue, (value) {
|
||||||
'Tarifa',
|
setState(() => tarifaValue = value);
|
||||||
false,
|
}),
|
||||||
(value) {
|
|
||||||
tarifaValue = value;
|
|
||||||
},
|
|
||||||
),
|
|
||||||
tarifaValue
|
tarifaValue
|
||||||
? Padding(
|
? Padding(
|
||||||
padding:
|
padding:
|
||||||
@@ -523,16 +344,11 @@ class _ProfileProScreenState extends State<ProfileProScreen> {
|
|||||||
],
|
],
|
||||||
),
|
),
|
||||||
],
|
],
|
||||||
)
|
).animate().moveY(duration: const Duration(milliseconds: 100)),
|
||||||
.animate()
|
|
||||||
.moveY(duration: const Duration(milliseconds: 100)),
|
|
||||||
)
|
)
|
||||||
: const SizedBox(),
|
: const SizedBox(),
|
||||||
if (settings?.domicilios == true)
|
if (settings?.domicilios == true)
|
||||||
const Divider(
|
const Divider(color: Colors.white, height: 12),
|
||||||
color: Colors.white,
|
|
||||||
height: 12,
|
|
||||||
),
|
|
||||||
if (settings?.domicilios == true)
|
if (settings?.domicilios == true)
|
||||||
customSwitch(
|
customSwitch(
|
||||||
'Servicio a domicilio', domicilioValue, toggleDomicilio),
|
'Servicio a domicilio', domicilioValue, toggleDomicilio),
|
||||||
@@ -557,9 +373,7 @@ class _ProfileProScreenState extends State<ProfileProScreen> {
|
|||||||
) as String?;
|
) as String?;
|
||||||
|
|
||||||
if (direccion != null) {
|
if (direccion != null) {
|
||||||
setState(() {
|
setState(() => _direccion = direccion);
|
||||||
_direccion = direccion;
|
|
||||||
});
|
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
decoration: InputDecoration(
|
decoration: InputDecoration(
|
||||||
@@ -576,9 +390,7 @@ class _ProfileProScreenState extends State<ProfileProScreen> {
|
|||||||
hintText: 'Oficina / Piso / Conjunto'),
|
hintText: 'Oficina / Piso / Conjunto'),
|
||||||
),
|
),
|
||||||
],
|
],
|
||||||
)
|
).animate().moveY(duration: const Duration(milliseconds: 100)),
|
||||||
.animate()
|
|
||||||
.moveY(duration: const Duration(milliseconds: 100)),
|
|
||||||
)
|
)
|
||||||
: const SizedBox(),
|
: const SizedBox(),
|
||||||
const Divider(),
|
const Divider(),
|
||||||
@@ -590,19 +402,14 @@ class _ProfileProScreenState extends State<ProfileProScreen> {
|
|||||||
children: [
|
children: [
|
||||||
const Text(
|
const Text(
|
||||||
'Metodos de pago',
|
'Metodos de pago',
|
||||||
style: TextStyle(
|
style: TextStyle(color: Colors.black, fontSize: 17),
|
||||||
color: Colors.black,
|
|
||||||
fontSize: 17,
|
|
||||||
),
|
|
||||||
),
|
),
|
||||||
for (var entry in paymentMethods.entries)
|
for (var entry in paymentMethods.entries)
|
||||||
PrimaryCheckbox(
|
PrimaryCheckbox(
|
||||||
text: entry.key,
|
text: entry.key,
|
||||||
initialValue: entry.value,
|
initialValue: entry.value,
|
||||||
onChanged: (value) {
|
onChanged: (value) {
|
||||||
setState(() {
|
setState(() => paymentMethods[entry.key] = value);
|
||||||
paymentMethods[entry.key] = value;
|
|
||||||
});
|
|
||||||
},
|
},
|
||||||
),
|
),
|
||||||
],
|
],
|
||||||
@@ -615,143 +422,20 @@ class _ProfileProScreenState extends State<ProfileProScreen> {
|
|||||||
width: double.infinity,
|
width: double.infinity,
|
||||||
child: Text(
|
child: Text(
|
||||||
'Horario estandar',
|
'Horario estandar',
|
||||||
style: TextStyle(
|
style: TextStyle(color: Colors.black, fontSize: 17),
|
||||||
color: Colors.black,
|
|
||||||
fontSize: 17,
|
|
||||||
),
|
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
GestureDetector(
|
GestureDetector(
|
||||||
onTap: () {
|
onTap: createSchedules,
|
||||||
createSchedules();
|
|
||||||
},
|
|
||||||
child: Padding(
|
child: Padding(
|
||||||
padding: const EdgeInsets.symmetric(vertical: 10),
|
padding: const EdgeInsets.symmetric(vertical: 10),
|
||||||
child: Table(
|
child: _scheduleTable(context),
|
||||||
defaultColumnWidth: const IntrinsicColumnWidth(),
|
|
||||||
children: [
|
|
||||||
TableRow(
|
|
||||||
children: [
|
|
||||||
TableCell(
|
|
||||||
child: Container(
|
|
||||||
padding: const EdgeInsets.all(8.0),
|
|
||||||
child: const Text('Lunes'),
|
|
||||||
),
|
|
||||||
),
|
|
||||||
TableCell(
|
|
||||||
child: Container(
|
|
||||||
padding: const EdgeInsets.all(8.0),
|
|
||||||
child: Text(timeList(_horarios?['1'], context)),
|
|
||||||
),
|
|
||||||
),
|
|
||||||
],
|
|
||||||
),
|
|
||||||
TableRow(
|
|
||||||
children: [
|
|
||||||
TableCell(
|
|
||||||
child: Container(
|
|
||||||
padding: const EdgeInsets.all(8.0),
|
|
||||||
child: const Text('Martes'),
|
|
||||||
),
|
|
||||||
),
|
|
||||||
TableCell(
|
|
||||||
child: Container(
|
|
||||||
padding: const EdgeInsets.all(8.0),
|
|
||||||
child: Text(timeList(_horarios?['2'], context)),
|
|
||||||
),
|
|
||||||
),
|
|
||||||
],
|
|
||||||
),
|
|
||||||
TableRow(
|
|
||||||
children: [
|
|
||||||
TableCell(
|
|
||||||
child: Container(
|
|
||||||
padding: const EdgeInsets.all(8.0),
|
|
||||||
child: const Text('Miercoles'),
|
|
||||||
),
|
|
||||||
),
|
|
||||||
TableCell(
|
|
||||||
child: Container(
|
|
||||||
padding: const EdgeInsets.all(8.0),
|
|
||||||
child: Text(timeList(_horarios?['3'], context)),
|
|
||||||
),
|
|
||||||
),
|
|
||||||
],
|
|
||||||
),
|
|
||||||
TableRow(
|
|
||||||
children: [
|
|
||||||
TableCell(
|
|
||||||
child: Container(
|
|
||||||
padding: const EdgeInsets.all(8.0),
|
|
||||||
child: const Text('Jueves'),
|
|
||||||
),
|
|
||||||
),
|
|
||||||
TableCell(
|
|
||||||
child: Container(
|
|
||||||
padding: const EdgeInsets.all(8.0),
|
|
||||||
child: Text(timeList(_horarios?['4'], context)),
|
|
||||||
),
|
|
||||||
),
|
|
||||||
],
|
|
||||||
),
|
|
||||||
TableRow(
|
|
||||||
children: [
|
|
||||||
TableCell(
|
|
||||||
child: Container(
|
|
||||||
padding: const EdgeInsets.all(8.0),
|
|
||||||
child: const Text('Viernes'),
|
|
||||||
),
|
|
||||||
),
|
|
||||||
TableCell(
|
|
||||||
child: Container(
|
|
||||||
padding: const EdgeInsets.all(8.0),
|
|
||||||
child: Text(timeList(_horarios?['5'], context)),
|
|
||||||
),
|
|
||||||
),
|
|
||||||
],
|
|
||||||
),
|
|
||||||
TableRow(
|
|
||||||
children: [
|
|
||||||
TableCell(
|
|
||||||
child: Container(
|
|
||||||
padding: const EdgeInsets.all(8.0),
|
|
||||||
child: const Text('Sabado'),
|
|
||||||
),
|
|
||||||
),
|
|
||||||
TableCell(
|
|
||||||
child: Container(
|
|
||||||
padding: const EdgeInsets.all(8.0),
|
|
||||||
child: Text(timeList(_horarios?['6'], context)),
|
|
||||||
),
|
|
||||||
),
|
|
||||||
],
|
|
||||||
),
|
|
||||||
TableRow(
|
|
||||||
children: [
|
|
||||||
TableCell(
|
|
||||||
child: Container(
|
|
||||||
padding: const EdgeInsets.all(8.0),
|
|
||||||
child: const Text('Domingo'),
|
|
||||||
),
|
|
||||||
),
|
|
||||||
TableCell(
|
|
||||||
child: Container(
|
|
||||||
padding: const EdgeInsets.all(8.0),
|
|
||||||
child: Text(timeList(_horarios?['7'], context)),
|
|
||||||
),
|
|
||||||
),
|
|
||||||
],
|
|
||||||
),
|
|
||||||
],
|
|
||||||
),
|
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
const SizedBox(height: 10),
|
const SizedBox(height: 10),
|
||||||
PrimaryButton(
|
PrimaryButton(
|
||||||
onPressed: () {
|
onPressed: updateInfo,
|
||||||
updateInfo();
|
|
||||||
},
|
|
||||||
text: 'Guardar',
|
text: 'Guardar',
|
||||||
),
|
),
|
||||||
const SizedBox(height: 20),
|
const SizedBox(height: 20),
|
||||||
@@ -761,6 +445,33 @@ class _ProfileProScreenState extends State<ProfileProScreen> {
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
Widget _scheduleTable(BuildContext context) {
|
||||||
|
final days = {
|
||||||
|
'1': 'Lunes',
|
||||||
|
'2': 'Martes',
|
||||||
|
'3': 'Miercoles',
|
||||||
|
'4': 'Jueves',
|
||||||
|
'5': 'Viernes',
|
||||||
|
'6': 'Sabado',
|
||||||
|
'7': 'Domingo',
|
||||||
|
};
|
||||||
|
return Table(
|
||||||
|
defaultColumnWidth: const IntrinsicColumnWidth(),
|
||||||
|
children: days.entries.map((e) {
|
||||||
|
return TableRow(children: [
|
||||||
|
TableCell(
|
||||||
|
child: Container(
|
||||||
|
padding: const EdgeInsets.all(8.0),
|
||||||
|
child: Text(e.value))),
|
||||||
|
TableCell(
|
||||||
|
child: Container(
|
||||||
|
padding: const EdgeInsets.all(8.0),
|
||||||
|
child: Text(timeList(_horarios?[e.key], context)))),
|
||||||
|
]);
|
||||||
|
}).toList(),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
Widget customSwitch(
|
Widget customSwitch(
|
||||||
String text,
|
String text,
|
||||||
bool switchValue,
|
bool switchValue,
|
||||||
@@ -784,10 +495,7 @@ class _ProfileProScreenState extends State<ProfileProScreen> {
|
|||||||
),
|
),
|
||||||
Transform.scale(
|
Transform.scale(
|
||||||
scale: 1.2,
|
scale: 1.2,
|
||||||
child: Switch(
|
child: Switch(value: switchValue, onChanged: onChanged),
|
||||||
value: switchValue,
|
|
||||||
onChanged: onChanged,
|
|
||||||
),
|
|
||||||
),
|
),
|
||||||
],
|
],
|
||||||
),
|
),
|
||||||
@@ -796,16 +504,10 @@ class _ProfileProScreenState extends State<ProfileProScreen> {
|
|||||||
}
|
}
|
||||||
|
|
||||||
String timeList(Schedule? schedule, BuildContext context) {
|
String timeList(Schedule? schedule, BuildContext context) {
|
||||||
if (schedule == null) {
|
if (schedule == null || !schedule.habilitado) return 'N/A';
|
||||||
return 'N/A';
|
|
||||||
}
|
|
||||||
if (!schedule.habilitado) {
|
|
||||||
return 'N/A';
|
|
||||||
}
|
|
||||||
if (schedule.jornadaContinua) {
|
if (schedule.jornadaContinua) {
|
||||||
return '${schedule.range1Hour1?.format(context).toString()} - ${schedule.range2Hour2?.format(context).toString()}';
|
return '${schedule.range1Hour1?.format(context)} - ${schedule.range2Hour2?.format(context)}';
|
||||||
} else {
|
|
||||||
return '${schedule.range1Hour1?.format(context).toString()} - ${schedule.range1Hour2?.format(context).toString()}; ${schedule.range2Hour1?.format(context).toString()} - ${schedule.range2Hour2?.format(context).toString()}';
|
|
||||||
}
|
}
|
||||||
|
return '${schedule.range1Hour1?.format(context)} - ${schedule.range1Hour2?.format(context)}; ${schedule.range2Hour1?.format(context)} - ${schedule.range2Hour2?.format(context)}';
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,6 +1,3 @@
|
|||||||
import 'dart:io';
|
|
||||||
import 'package:cloud_firestore/cloud_firestore.dart';
|
|
||||||
import 'package:firebase_storage/firebase_storage.dart';
|
|
||||||
import 'package:flutter/cupertino.dart';
|
import 'package:flutter/cupertino.dart';
|
||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
import 'package:flutter/services.dart';
|
import 'package:flutter/services.dart';
|
||||||
@@ -12,8 +9,8 @@ import 'package:prosappco/src/components/primary_btn.dart';
|
|||||||
import 'package:prosappco/src/components/schedule_picker.dart';
|
import 'package:prosappco/src/components/schedule_picker.dart';
|
||||||
import 'package:prosappco/src/models/setting_model.dart';
|
import 'package:prosappco/src/models/setting_model.dart';
|
||||||
import 'package:prosappco/src/presentation/screens/horario.dart';
|
import 'package:prosappco/src/presentation/screens/horario.dart';
|
||||||
import 'package:prosappco/src/presentation/screens/professional.dart';
|
|
||||||
import 'package:prosappco/src/presentation/screens/ubicacion.dart';
|
import 'package:prosappco/src/presentation/screens/ubicacion.dart';
|
||||||
|
import 'package:prosappco/src/services/api_service.dart';
|
||||||
|
|
||||||
class ProfileProWebScreen extends StatefulWidget {
|
class ProfileProWebScreen extends StatefulWidget {
|
||||||
const ProfileProWebScreen({super.key});
|
const ProfileProWebScreen({super.key});
|
||||||
@@ -36,11 +33,8 @@ class _ProfileProWebScreenState extends State<ProfileProWebScreen> {
|
|||||||
bool domicilioValue = true;
|
bool domicilioValue = true;
|
||||||
bool tarifaValue = false;
|
bool tarifaValue = false;
|
||||||
bool sitioValue = false;
|
bool sitioValue = false;
|
||||||
var photoTemp = '';
|
|
||||||
|
|
||||||
var _direccion = '...';
|
|
||||||
var _ubicacion = '...';
|
var _ubicacion = '...';
|
||||||
var _opcionalAddress = '...';
|
|
||||||
int _tarifa = 0;
|
int _tarifa = 0;
|
||||||
SettingModel? settings;
|
SettingModel? settings;
|
||||||
|
|
||||||
@@ -49,110 +43,78 @@ class _ProfileProWebScreenState extends State<ProfileProWebScreen> {
|
|||||||
@override
|
@override
|
||||||
void initState() {
|
void initState() {
|
||||||
super.initState();
|
super.initState();
|
||||||
if (settings == null) {
|
SettingModel.getSettings()
|
||||||
SettingModel.getSettings().then(
|
.then((v) => setState(() => settings = v));
|
||||||
(SettingModel value) => setState(() => settings = value),
|
Schedule.getHorarios(uid.toString())
|
||||||
);
|
.then((data) => setState(() => _horarios = data));
|
||||||
}
|
_loadProData();
|
||||||
|
}
|
||||||
|
|
||||||
final uid = AuthenticationRepository.instance.getCurrentUserUid();
|
Future<void> _loadProData() async {
|
||||||
|
try {
|
||||||
|
final Map<String, dynamic> data =
|
||||||
|
await ApiService.instance.get('/auth/me');
|
||||||
|
final pro = data['professionals'] as Map<String, dynamic>?;
|
||||||
|
|
||||||
if (_horarios == null) {
|
if (mounted) {
|
||||||
Schedule.getHorarios(uid.toString()).then(
|
setState(() {
|
||||||
(Map<String, Schedule> data) {
|
final address = data['address'] ?? '';
|
||||||
setState(() {
|
if (address.isNotEmpty) {
|
||||||
_horarios = data;
|
_ubicationController.text = address;
|
||||||
});
|
}
|
||||||
},
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (_direccion == '...') {
|
final opcAddr =
|
||||||
AuthenticationRepository.instance
|
pro?['opcional_address'] ?? data['opcional_address'] ?? '';
|
||||||
.getAddress(uid.toString())
|
if (opcAddr.isNotEmpty) {
|
||||||
.then((String s) => setState(() {
|
_opcionalAddressController.text = opcAddr;
|
||||||
_direccion = s;
|
}
|
||||||
_ubicationController.text = _direccion;
|
|
||||||
}));
|
|
||||||
}
|
|
||||||
if (_ubicacion == '...') {
|
|
||||||
AuthenticationRepository.instance.getUbicacion(uid.toString()).then(
|
|
||||||
(String s) => setState(
|
|
||||||
() {
|
|
||||||
_ubicacion = s;
|
|
||||||
if (_ubicacion == 'ambos') {
|
|
||||||
sitioValue = true;
|
|
||||||
domicilioValue = true;
|
|
||||||
} else if (_ubicacion == 'sitio') {
|
|
||||||
sitioValue = true;
|
|
||||||
} else if (_ubicacion == 'domicilio') {
|
|
||||||
domicilioValue = true;
|
|
||||||
}
|
|
||||||
},
|
|
||||||
),
|
|
||||||
);
|
|
||||||
}
|
|
||||||
if (_opcionalAddress == '...') {
|
|
||||||
AuthenticationRepository.instance.getOpcionalAddress(uid.toString()).then(
|
|
||||||
(String s) => setState(
|
|
||||||
() {
|
|
||||||
_opcionalAddress = s;
|
|
||||||
|
|
||||||
if (_opcionalAddress != '...') {
|
_ubicacion = pro?['ubicacion'] ?? data['ubicacion'] ?? '...';
|
||||||
_opcionalAddressController.text = _opcionalAddress;
|
if (_ubicacion == 'ambos') {
|
||||||
}
|
sitioValue = true;
|
||||||
},
|
domicilioValue = true;
|
||||||
),
|
} else if (_ubicacion == 'sitio') {
|
||||||
);
|
sitioValue = true;
|
||||||
}
|
} else if (_ubicacion == 'domicilio') {
|
||||||
if (_tarifa == 0) {
|
domicilioValue = true;
|
||||||
AuthenticationRepository.instance.getTarifa(uid.toString()).then(
|
}
|
||||||
(s) => setState(
|
|
||||||
() {
|
|
||||||
_tarifa = s;
|
|
||||||
|
|
||||||
if (_tarifa != 0) {
|
_tarifa = pro?['rate'] != null
|
||||||
tarifaValue = true;
|
? (double.tryParse(pro!['rate'].toString())?.toInt() ?? 0)
|
||||||
_tarifaController.text = _tarifa.toString();
|
: 0;
|
||||||
}
|
if (_tarifa != 0) {
|
||||||
},
|
tarifaValue = true;
|
||||||
),
|
_tarifaController.text = _tarifa.toString();
|
||||||
);
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
} catch (e) {
|
||||||
|
print('Error loading pro data: $e');
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
Future<void> updateInfo() async {
|
Future<void> updateInfo() async {
|
||||||
|
final Map<String, dynamic> body = {};
|
||||||
|
|
||||||
if (_ubicationController.text.isNotEmpty) {
|
if (_ubicationController.text.isNotEmpty) {
|
||||||
FirebaseFirestore.instance.collection('users').doc(uid).update({
|
body['address'] = _ubicationController.text;
|
||||||
'address': _ubicationController.text,
|
if (latUser != 0.0) {
|
||||||
if (latUser != 0.0) 'latitude': latUser,
|
body['latitude'] = latUser;
|
||||||
if (latUser != 0.0) 'longitude': lngUser,
|
body['longitude'] = lngUser;
|
||||||
});
|
}
|
||||||
}
|
}
|
||||||
if (_opcionalAddressController.text.isNotEmpty) {
|
if (_opcionalAddressController.text.isNotEmpty) {
|
||||||
FirebaseFirestore.instance
|
body['opcional_address'] = _opcionalAddressController.text;
|
||||||
.collection('users')
|
|
||||||
.doc(uid)
|
|
||||||
.update({'opcional_address': _opcionalAddressController.text});
|
|
||||||
}
|
|
||||||
if (tarifaValue && _tarifaController.text.isNotEmpty) {
|
|
||||||
FirebaseFirestore.instance
|
|
||||||
.collection('users')
|
|
||||||
.doc(uid)
|
|
||||||
.update({'tarifa': int.parse(_tarifaController.text)});
|
|
||||||
} else {
|
|
||||||
FirebaseFirestore.instance
|
|
||||||
.collection('users')
|
|
||||||
.doc(uid)
|
|
||||||
.update({'tarifa': 0});
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
body['rate'] = tarifaValue && _tarifaController.text.isNotEmpty
|
||||||
|
? int.parse(_tarifaController.text)
|
||||||
|
: 0;
|
||||||
|
|
||||||
|
String? ubicacion;
|
||||||
if (settings?.domicilios == false) {
|
if (settings?.domicilios == false) {
|
||||||
if (sitioValue) {
|
if (sitioValue) {
|
||||||
FirebaseFirestore.instance
|
ubicacion = 'sitio';
|
||||||
.collection('users')
|
|
||||||
.doc(uid)
|
|
||||||
.update({'ubicacion': 'sitio'});
|
|
||||||
} else {
|
} else {
|
||||||
Get.snackbar(
|
Get.snackbar(
|
||||||
'Elige como vas a dar tu servicio',
|
'Elige como vas a dar tu servicio',
|
||||||
@@ -164,29 +126,15 @@ class _ProfileProWebScreenState extends State<ProfileProWebScreen> {
|
|||||||
style: TextStyle(color: Colors.white),
|
style: TextStyle(color: Colors.white),
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
|
|
||||||
FirebaseFirestore.instance
|
|
||||||
.collection('users')
|
|
||||||
.doc(uid)
|
|
||||||
.update({'ubicacion': null});
|
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
if (domicilioValue && sitioValue) {
|
if (domicilioValue && sitioValue) {
|
||||||
FirebaseFirestore.instance
|
ubicacion = 'ambos';
|
||||||
.collection('users')
|
|
||||||
.doc(uid)
|
|
||||||
.update({'ubicacion': 'ambos'});
|
|
||||||
} else if (domicilioValue) {
|
} else if (domicilioValue) {
|
||||||
FirebaseFirestore.instance
|
ubicacion = 'domicilio';
|
||||||
.collection('users')
|
|
||||||
.doc(uid)
|
|
||||||
.update({'ubicacion': 'domicilio'});
|
|
||||||
} else if (sitioValue) {
|
} else if (sitioValue) {
|
||||||
FirebaseFirestore.instance
|
ubicacion = 'sitio';
|
||||||
.collection('users')
|
|
||||||
.doc(uid)
|
|
||||||
.update({'ubicacion': 'sitio'});
|
|
||||||
} else {
|
} else {
|
||||||
Get.snackbar(
|
Get.snackbar(
|
||||||
'Elige como vas a dar tu servicio',
|
'Elige como vas a dar tu servicio',
|
||||||
@@ -201,6 +149,13 @@ class _ProfileProWebScreenState extends State<ProfileProWebScreen> {
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
body['ubicacion'] = ubicacion;
|
||||||
|
|
||||||
|
try {
|
||||||
|
await ApiService.instance.patch('/users/me', body);
|
||||||
|
} catch (e) {
|
||||||
|
print('Error updating pro info: $e');
|
||||||
|
}
|
||||||
|
|
||||||
if (settings?.domicilios == false && sitioValue == false) {
|
if (settings?.domicilios == false && sitioValue == false) {
|
||||||
Get.defaultDialog(
|
Get.defaultDialog(
|
||||||
@@ -209,9 +164,7 @@ class _ProfileProWebScreenState extends State<ProfileProWebScreen> {
|
|||||||
'Si no eliges servicio en sitio, no serás visible para los usuarios.',
|
'Si no eliges servicio en sitio, no serás visible para los usuarios.',
|
||||||
actions: [
|
actions: [
|
||||||
ElevatedButton(
|
ElevatedButton(
|
||||||
onPressed: () {
|
onPressed: () => Get.back(),
|
||||||
Get.back();
|
|
||||||
},
|
|
||||||
child: const Text('Entendido'),
|
child: const Text('Entendido'),
|
||||||
),
|
),
|
||||||
],
|
],
|
||||||
@@ -224,97 +177,24 @@ class _ProfileProWebScreenState extends State<ProfileProWebScreen> {
|
|||||||
);
|
);
|
||||||
Navigator.pop(context);
|
Navigator.pop(context);
|
||||||
}
|
}
|
||||||
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
Future<void> updateImage(image) async {
|
|
||||||
try {
|
|
||||||
await FirebaseFirestore.instance
|
|
||||||
.collection('users')
|
|
||||||
.doc(uid)
|
|
||||||
.update({'banner': image});
|
|
||||||
} catch (e) {
|
|
||||||
try {
|
|
||||||
await FirebaseFirestore.instance
|
|
||||||
.collection('users')
|
|
||||||
.doc(uid)
|
|
||||||
.set({'banner': image});
|
|
||||||
} catch (e) {
|
|
||||||
print('Error al agregar la imagen de perfil: $e');
|
|
||||||
}
|
|
||||||
|
|
||||||
print('Error al actualizar la imagen de perfil: $e');
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
void createSchedules() async {
|
void createSchedules() async {
|
||||||
if (_horarios == null || _horarios!.isEmpty) {
|
if (_horarios == null || _horarios!.isEmpty) {
|
||||||
final defaultSchedule = {
|
final Map<String, dynamic> defaultSchedule = {};
|
||||||
'1': {
|
for (var i = 1; i <= 7; i++) {
|
||||||
|
defaultSchedule['$i'] = {
|
||||||
'habilitado': false,
|
'habilitado': false,
|
||||||
'jornadaContinua': false,
|
'jornadaContinua': false,
|
||||||
'range1Hour1': null,
|
'range1Hour1': null,
|
||||||
'range1Hour2': null,
|
'range1Hour2': null,
|
||||||
'range2Hour1': null,
|
'range2Hour1': null,
|
||||||
'range2Hour2': null
|
'range2Hour2': null,
|
||||||
},
|
};
|
||||||
'2': {
|
}
|
||||||
'habilitado': false,
|
|
||||||
'jornadaContinua': false,
|
|
||||||
'range1Hour1': null,
|
|
||||||
'range1Hour2': null,
|
|
||||||
'range2Hour1': null,
|
|
||||||
'range2Hour2': null
|
|
||||||
},
|
|
||||||
'3': {
|
|
||||||
'habilitado': false,
|
|
||||||
'jornadaContinua': false,
|
|
||||||
'range1Hour1': null,
|
|
||||||
'range1Hour2': null,
|
|
||||||
'range2Hour1': null,
|
|
||||||
'range2Hour2': null
|
|
||||||
},
|
|
||||||
'4': {
|
|
||||||
'habilitado': false,
|
|
||||||
'jornadaContinua': false,
|
|
||||||
'range1Hour1': null,
|
|
||||||
'range1Hour2': null,
|
|
||||||
'range2Hour1': null,
|
|
||||||
'range2Hour2': null
|
|
||||||
},
|
|
||||||
'5': {
|
|
||||||
'habilitado': false,
|
|
||||||
'jornadaContinua': false,
|
|
||||||
'range1Hour1': null,
|
|
||||||
'range1Hour2': null,
|
|
||||||
'range2Hour1': null,
|
|
||||||
'range2Hour2': null
|
|
||||||
},
|
|
||||||
'6': {
|
|
||||||
'habilitado': false,
|
|
||||||
'jornadaContinua': false,
|
|
||||||
'range1Hour1': null,
|
|
||||||
'range1Hour2': null,
|
|
||||||
'range2Hour1': null,
|
|
||||||
'range2Hour2': null
|
|
||||||
},
|
|
||||||
'7': {
|
|
||||||
'habilitado': false,
|
|
||||||
'jornadaContinua': false,
|
|
||||||
'range1Hour1': null,
|
|
||||||
'range1Hour2': null,
|
|
||||||
'range2Hour1': null,
|
|
||||||
'range2Hour2': null
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
try {
|
try {
|
||||||
await FirebaseFirestore.instance
|
await ApiService.instance
|
||||||
.collection('users')
|
.patch('/users/me', {'horario': defaultSchedule});
|
||||||
.doc(uid)
|
|
||||||
.update({'horario': defaultSchedule});
|
|
||||||
|
|
||||||
Navigator.pop(context);
|
Navigator.pop(context);
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
print(e);
|
print(e);
|
||||||
@@ -324,53 +204,24 @@ class _ProfileProWebScreenState extends State<ProfileProWebScreen> {
|
|||||||
context,
|
context,
|
||||||
CupertinoPageRoute(
|
CupertinoPageRoute(
|
||||||
builder: (BuildContext context) {
|
builder: (BuildContext context) {
|
||||||
return HorarioScreen(
|
return HorarioScreen(horarios: _horarios!);
|
||||||
horarios: _horarios!,
|
|
||||||
);
|
|
||||||
},
|
},
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
Future<bool> uploadImage(File image) async {
|
|
||||||
final String namefile = image.path.split('/').last;
|
|
||||||
|
|
||||||
Reference ref = storage
|
|
||||||
.ref()
|
|
||||||
.child('users')
|
|
||||||
.child(uid!)
|
|
||||||
.child('profile')
|
|
||||||
.child(namefile);
|
|
||||||
|
|
||||||
final UploadTask uploadTask = ref.putFile(image);
|
|
||||||
|
|
||||||
final TaskSnapshot snapshot = await uploadTask.whenComplete(() => true);
|
|
||||||
|
|
||||||
photoTemp = ref.fullPath;
|
|
||||||
|
|
||||||
if (snapshot.state == TaskState.success) {
|
|
||||||
return true;
|
|
||||||
} else {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
void toggleDomicilio(bool newValue) {
|
void toggleDomicilio(bool newValue) {
|
||||||
setState(() {
|
setState(() {
|
||||||
domicilioValue = newValue;
|
domicilioValue = newValue;
|
||||||
if (newValue == false && sitioValue == false) {
|
if (newValue == false && sitioValue == false) sitioValue = true;
|
||||||
sitioValue = true;
|
|
||||||
}
|
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
void toggleSitio(bool newValue) {
|
void toggleSitio(bool newValue) {
|
||||||
setState(() {
|
setState(() {
|
||||||
sitioValue = newValue;
|
sitioValue = newValue;
|
||||||
if (newValue == false && domicilioValue == false) {
|
if (newValue == false && domicilioValue == false) domicilioValue = true;
|
||||||
domicilioValue = true;
|
|
||||||
}
|
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -378,27 +229,18 @@ class _ProfileProWebScreenState extends State<ProfileProWebScreen> {
|
|||||||
Widget build(BuildContext context) {
|
Widget build(BuildContext context) {
|
||||||
return Scaffold(
|
return Scaffold(
|
||||||
appBar: PopAppbar(
|
appBar: PopAppbar(
|
||||||
onPressed: () {
|
onPressed: () => Navigator.pop(context),
|
||||||
Navigator.pop(context);
|
|
||||||
},
|
|
||||||
label: 'Perfil profesional'),
|
label: 'Perfil profesional'),
|
||||||
body: SingleChildScrollView(
|
body: SingleChildScrollView(
|
||||||
reverse: true,
|
reverse: true,
|
||||||
child: Column(
|
child: Column(
|
||||||
children: [
|
children: [
|
||||||
if (settings?.tarifas == true)
|
if (settings?.tarifas == true)
|
||||||
const Divider(
|
const Divider(color: Colors.white, height: 12),
|
||||||
color: Colors.white,
|
|
||||||
height: 12,
|
|
||||||
),
|
|
||||||
if (settings?.tarifas == true)
|
if (settings?.tarifas == true)
|
||||||
customSwitch(
|
customSwitch('Tarifa', tarifaValue, (value) {
|
||||||
'Tarifa',
|
setState(() => tarifaValue = value);
|
||||||
tarifaValue,
|
}),
|
||||||
(value) {
|
|
||||||
tarifaValue = value;
|
|
||||||
},
|
|
||||||
),
|
|
||||||
tarifaValue
|
tarifaValue
|
||||||
? Padding(
|
? Padding(
|
||||||
padding:
|
padding:
|
||||||
@@ -416,16 +258,11 @@ class _ProfileProWebScreenState extends State<ProfileProWebScreen> {
|
|||||||
],
|
],
|
||||||
),
|
),
|
||||||
],
|
],
|
||||||
)
|
).animate().moveY(duration: const Duration(milliseconds: 100)),
|
||||||
.animate()
|
|
||||||
.moveY(duration: const Duration(milliseconds: 100)),
|
|
||||||
)
|
)
|
||||||
: const SizedBox(),
|
: const SizedBox(),
|
||||||
if (settings?.domicilios == true)
|
if (settings?.domicilios == true)
|
||||||
const Divider(
|
const Divider(color: Colors.white, height: 12),
|
||||||
color: Colors.white,
|
|
||||||
height: 12,
|
|
||||||
),
|
|
||||||
if (settings?.domicilios == true)
|
if (settings?.domicilios == true)
|
||||||
customSwitch(
|
customSwitch(
|
||||||
'Servicio a domicilio', domicilioValue, toggleDomicilio),
|
'Servicio a domicilio', domicilioValue, toggleDomicilio),
|
||||||
@@ -474,9 +311,7 @@ class _ProfileProWebScreenState extends State<ProfileProWebScreen> {
|
|||||||
hintText: 'Oficina / Piso / Conjunto'),
|
hintText: 'Oficina / Piso / Conjunto'),
|
||||||
),
|
),
|
||||||
],
|
],
|
||||||
)
|
).animate().moveY(duration: const Duration(milliseconds: 100)),
|
||||||
.animate()
|
|
||||||
.moveY(duration: const Duration(milliseconds: 100)),
|
|
||||||
)
|
)
|
||||||
: const SizedBox(),
|
: const SizedBox(),
|
||||||
const Divider(),
|
const Divider(),
|
||||||
@@ -486,146 +321,43 @@ class _ProfileProWebScreenState extends State<ProfileProWebScreen> {
|
|||||||
width: double.infinity,
|
width: double.infinity,
|
||||||
child: Text(
|
child: Text(
|
||||||
'Horario estandar',
|
'Horario estandar',
|
||||||
style: TextStyle(
|
style: TextStyle(color: Colors.black, fontSize: 15),
|
||||||
color: Colors.black,
|
|
||||||
fontSize: 15,
|
|
||||||
),
|
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
GestureDetector(
|
GestureDetector(
|
||||||
onTap: () {
|
onTap: createSchedules,
|
||||||
createSchedules();
|
|
||||||
},
|
|
||||||
child: Padding(
|
child: Padding(
|
||||||
padding: const EdgeInsets.symmetric(vertical: 10),
|
padding: const EdgeInsets.symmetric(vertical: 10),
|
||||||
child: Table(
|
child: Table(
|
||||||
defaultColumnWidth: const IntrinsicColumnWidth(),
|
defaultColumnWidth: const IntrinsicColumnWidth(),
|
||||||
children: [
|
children: [
|
||||||
TableRow(
|
for (var entry in {
|
||||||
children: [
|
'1': 'Lunes',
|
||||||
|
'2': 'Martes',
|
||||||
|
'3': 'Miercoles',
|
||||||
|
'4': 'Jueves',
|
||||||
|
'5': 'Viernes',
|
||||||
|
'6': 'Sabado',
|
||||||
|
'7': 'Domingo',
|
||||||
|
}.entries)
|
||||||
|
TableRow(children: [
|
||||||
TableCell(
|
TableCell(
|
||||||
child: Container(
|
child: Container(
|
||||||
padding: const EdgeInsets.all(8.0),
|
padding: const EdgeInsets.all(8.0),
|
||||||
child: const Text('Lunes'),
|
child: Text(entry.value))),
|
||||||
),
|
|
||||||
),
|
|
||||||
TableCell(
|
TableCell(
|
||||||
child: Container(
|
child: Container(
|
||||||
padding: const EdgeInsets.all(8.0),
|
padding: const EdgeInsets.all(8.0),
|
||||||
child: Text(timeList(_horarios?['1'], context)),
|
child:
|
||||||
),
|
Text(timeList(_horarios?[entry.key], context)))),
|
||||||
),
|
]),
|
||||||
],
|
|
||||||
),
|
|
||||||
TableRow(
|
|
||||||
children: [
|
|
||||||
TableCell(
|
|
||||||
child: Container(
|
|
||||||
padding: const EdgeInsets.all(8.0),
|
|
||||||
child: const Text('Martes'),
|
|
||||||
),
|
|
||||||
),
|
|
||||||
TableCell(
|
|
||||||
child: Container(
|
|
||||||
padding: const EdgeInsets.all(8.0),
|
|
||||||
child: Text(timeList(_horarios?['2'], context)),
|
|
||||||
),
|
|
||||||
),
|
|
||||||
],
|
|
||||||
),
|
|
||||||
TableRow(
|
|
||||||
children: [
|
|
||||||
TableCell(
|
|
||||||
child: Container(
|
|
||||||
padding: const EdgeInsets.all(8.0),
|
|
||||||
child: const Text('Miercoles'),
|
|
||||||
),
|
|
||||||
),
|
|
||||||
TableCell(
|
|
||||||
child: Container(
|
|
||||||
padding: const EdgeInsets.all(8.0),
|
|
||||||
child: Text(timeList(_horarios?['3'], context)),
|
|
||||||
),
|
|
||||||
),
|
|
||||||
],
|
|
||||||
),
|
|
||||||
TableRow(
|
|
||||||
children: [
|
|
||||||
TableCell(
|
|
||||||
child: Container(
|
|
||||||
padding: const EdgeInsets.all(8.0),
|
|
||||||
child: const Text('Jueves'),
|
|
||||||
),
|
|
||||||
),
|
|
||||||
TableCell(
|
|
||||||
child: Container(
|
|
||||||
padding: const EdgeInsets.all(8.0),
|
|
||||||
child: Text(timeList(_horarios?['4'], context)),
|
|
||||||
),
|
|
||||||
),
|
|
||||||
],
|
|
||||||
),
|
|
||||||
TableRow(
|
|
||||||
children: [
|
|
||||||
TableCell(
|
|
||||||
child: Container(
|
|
||||||
padding: const EdgeInsets.all(8.0),
|
|
||||||
child: const Text('Viernes'),
|
|
||||||
),
|
|
||||||
),
|
|
||||||
TableCell(
|
|
||||||
child: Container(
|
|
||||||
padding: const EdgeInsets.all(8.0),
|
|
||||||
child: Text(timeList(_horarios?['5'], context)),
|
|
||||||
),
|
|
||||||
),
|
|
||||||
],
|
|
||||||
),
|
|
||||||
TableRow(
|
|
||||||
children: [
|
|
||||||
TableCell(
|
|
||||||
child: Container(
|
|
||||||
padding: const EdgeInsets.all(8.0),
|
|
||||||
child: const Text('Sabado'),
|
|
||||||
),
|
|
||||||
),
|
|
||||||
TableCell(
|
|
||||||
child: Container(
|
|
||||||
padding: const EdgeInsets.all(8.0),
|
|
||||||
child: Text(timeList(_horarios?['6'], context)),
|
|
||||||
),
|
|
||||||
),
|
|
||||||
],
|
|
||||||
),
|
|
||||||
TableRow(
|
|
||||||
children: [
|
|
||||||
TableCell(
|
|
||||||
child: Container(
|
|
||||||
padding: const EdgeInsets.all(8.0),
|
|
||||||
child: const Text('Domingo'),
|
|
||||||
),
|
|
||||||
),
|
|
||||||
TableCell(
|
|
||||||
child: Container(
|
|
||||||
padding: const EdgeInsets.all(8.0),
|
|
||||||
child: Text(timeList(_horarios?['7'], context)),
|
|
||||||
),
|
|
||||||
),
|
|
||||||
],
|
|
||||||
),
|
|
||||||
],
|
],
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
PrimaryButtom(
|
PrimaryButtom(onPressed: updateInfo, label: 'Guardar'),
|
||||||
onPressed: () {
|
const SizedBox(height: 20),
|
||||||
updateInfo();
|
|
||||||
},
|
|
||||||
label: 'Guardar'),
|
|
||||||
const SizedBox(
|
|
||||||
height: 20,
|
|
||||||
)
|
|
||||||
],
|
],
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
@@ -643,18 +375,12 @@ class _ProfileProWebScreenState extends State<ProfileProWebScreen> {
|
|||||||
Expanded(
|
Expanded(
|
||||||
child: Text(
|
child: Text(
|
||||||
text,
|
text,
|
||||||
style: const TextStyle(
|
style: const TextStyle(fontSize: 15, color: Colors.black),
|
||||||
fontSize: 15,
|
|
||||||
color: Colors.black,
|
|
||||||
),
|
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
Transform.scale(
|
Transform.scale(
|
||||||
scale: 1.2,
|
scale: 1.2,
|
||||||
child: Switch(
|
child: Switch(value: switchValue, onChanged: onChanged),
|
||||||
value: switchValue,
|
|
||||||
onChanged: onChanged,
|
|
||||||
),
|
|
||||||
),
|
),
|
||||||
],
|
],
|
||||||
),
|
),
|
||||||
@@ -663,16 +389,10 @@ class _ProfileProWebScreenState extends State<ProfileProWebScreen> {
|
|||||||
}
|
}
|
||||||
|
|
||||||
String timeList(Schedule? schedule, BuildContext context) {
|
String timeList(Schedule? schedule, BuildContext context) {
|
||||||
if (schedule == null) {
|
if (schedule == null || !schedule.habilitado) return 'N/A';
|
||||||
return 'N/A';
|
|
||||||
}
|
|
||||||
if (!schedule.habilitado) {
|
|
||||||
return 'N/A';
|
|
||||||
}
|
|
||||||
if (schedule.jornadaContinua) {
|
if (schedule.jornadaContinua) {
|
||||||
return '${schedule.range1Hour1?.format(context).toString()} - ${schedule.range2Hour2?.format(context).toString()}';
|
return '${schedule.range1Hour1?.format(context)} - ${schedule.range2Hour2?.format(context)}';
|
||||||
} else {
|
|
||||||
return '${schedule.range1Hour1?.format(context).toString()} - ${schedule.range1Hour2?.format(context).toString()}; ${schedule.range2Hour1?.format(context).toString()} - ${schedule.range2Hour2?.format(context).toString()}';
|
|
||||||
}
|
}
|
||||||
|
return '${schedule.range1Hour1?.format(context)} - ${schedule.range1Hour2?.format(context)}; ${schedule.range2Hour1?.format(context)} - ${schedule.range2Hour2?.format(context)}';
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,9 +1,7 @@
|
|||||||
import 'package:flutter/cupertino.dart';
|
import 'package:flutter/cupertino.dart';
|
||||||
import 'package:flutter/foundation.dart';
|
import 'package:flutter/foundation.dart';
|
||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
import 'package:font_awesome_flutter/font_awesome_flutter.dart';
|
|
||||||
import 'package:get/get.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/components/bottom_sheet.dart';
|
||||||
import 'package:prosappco/src/components/column_padding.dart';
|
import 'package:prosappco/src/components/column_padding.dart';
|
||||||
import 'package:prosappco/src/presentation/widgets/shared/primary_button.dart';
|
import 'package:prosappco/src/presentation/widgets/shared/primary_button.dart';
|
||||||
@@ -90,68 +88,54 @@ class _RegisterScreenState extends State<RegisterScreen> {
|
|||||||
alineacion: MainAxisAlignment.start,
|
alineacion: MainAxisAlignment.start,
|
||||||
padding: const EdgeInsets.symmetric(horizontal: 25, vertical: 20),
|
padding: const EdgeInsets.symmetric(horizontal: 25, vertical: 20),
|
||||||
children: [
|
children: [
|
||||||
!isIOS && !kIsWeb && settings?.google == true
|
const SizedBox(),
|
||||||
? Padding(
|
|
||||||
padding: const EdgeInsets.only(bottom: 20),
|
|
||||||
child: ElevatedButton(
|
|
||||||
onPressed: () async {
|
|
||||||
await AuthenticationRepository.instance
|
|
||||||
.signInWithGoogle();
|
|
||||||
},
|
|
||||||
style: ElevatedButton.styleFrom(
|
|
||||||
backgroundColor: const Color(0xFF2BA4EC),
|
|
||||||
shape: RoundedRectangleBorder(
|
|
||||||
borderRadius: BorderRadius.circular(50),
|
|
||||||
),
|
|
||||||
elevation: 0,
|
|
||||||
minimumSize: const Size(230, 60),
|
|
||||||
),
|
|
||||||
child: const Row(
|
|
||||||
mainAxisAlignment: MainAxisAlignment.center,
|
|
||||||
children: [
|
|
||||||
Text(
|
|
||||||
'Entrar con Google ',
|
|
||||||
style: TextStyle(
|
|
||||||
color: Colors.white,
|
|
||||||
fontWeight: FontWeight.bold,
|
|
||||||
fontSize: 18,
|
|
||||||
),
|
|
||||||
),
|
|
||||||
SizedBox(width: 5),
|
|
||||||
FaIcon(FontAwesomeIcons.google),
|
|
||||||
],
|
|
||||||
)),
|
|
||||||
)
|
|
||||||
: const SizedBox(),
|
|
||||||
!isIOS && !kIsWeb && settings?.google == true
|
|
||||||
? const Padding(
|
|
||||||
padding: EdgeInsets.symmetric(vertical: 0),
|
|
||||||
child: Row(
|
|
||||||
children: <Widget>[
|
|
||||||
Expanded(
|
|
||||||
child: Divider(
|
|
||||||
color: Colors.black38,
|
|
||||||
thickness: 1,
|
|
||||||
),
|
|
||||||
),
|
|
||||||
Padding(
|
|
||||||
padding: EdgeInsets.symmetric(horizontal: 10),
|
|
||||||
child: Text("ó"),
|
|
||||||
),
|
|
||||||
Expanded(
|
|
||||||
child: Divider(
|
|
||||||
color: Colors.black38,
|
|
||||||
thickness: 1,
|
|
||||||
),
|
|
||||||
),
|
|
||||||
],
|
|
||||||
),
|
|
||||||
)
|
|
||||||
: const SizedBox(),
|
|
||||||
Form(
|
Form(
|
||||||
key: _formKey,
|
key: _formKey,
|
||||||
child: Column(
|
child: Column(
|
||||||
children: [
|
children: [
|
||||||
|
const Padding(
|
||||||
|
padding: EdgeInsets.only(bottom: 5),
|
||||||
|
child: Align(
|
||||||
|
alignment: Alignment.topLeft,
|
||||||
|
child: Text('Nombre',
|
||||||
|
style: TextStyle(
|
||||||
|
fontSize: 18.0, color: Color(0xFF65676B))),
|
||||||
|
)),
|
||||||
|
Padding(
|
||||||
|
padding: const EdgeInsets.only(bottom: 18),
|
||||||
|
child: TextFormField(
|
||||||
|
controller: controller.name,
|
||||||
|
validator: (String? value) {
|
||||||
|
if (value == null || value.isEmpty) {
|
||||||
|
return 'Por favor ingresa tu nombre';
|
||||||
|
}
|
||||||
|
return null;
|
||||||
|
},
|
||||||
|
decoration: const InputDecoration(
|
||||||
|
border: OutlineInputBorder(
|
||||||
|
borderSide: BorderSide(color: Color(0xFFECECEC)),
|
||||||
|
borderRadius: BorderRadius.all(Radius.circular(50)),
|
||||||
|
),
|
||||||
|
enabledBorder: OutlineInputBorder(
|
||||||
|
borderSide: BorderSide(color: Color(0xFFECECEC)),
|
||||||
|
borderRadius: BorderRadius.all(Radius.circular(50)),
|
||||||
|
),
|
||||||
|
focusedBorder: OutlineInputBorder(
|
||||||
|
borderSide: BorderSide(color: Color(0xFFECECEC)),
|
||||||
|
borderRadius: BorderRadius.all(Radius.circular(50)),
|
||||||
|
),
|
||||||
|
errorBorder: OutlineInputBorder(
|
||||||
|
borderSide: BorderSide(color: Color.fromARGB(255, 184, 0, 0)),
|
||||||
|
borderRadius: BorderRadius.all(Radius.circular(50)),
|
||||||
|
),
|
||||||
|
hintText: 'Tu nombre completo',
|
||||||
|
fillColor: Color.fromARGB(255, 239, 239, 239),
|
||||||
|
filled: true,
|
||||||
|
prefixIcon: Icon(Icons.person_outline),
|
||||||
|
hintStyle: TextStyle(color: Colors.grey),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
const Padding(
|
const Padding(
|
||||||
padding: EdgeInsets.only(bottom: 5),
|
padding: EdgeInsets.only(bottom: 5),
|
||||||
child: Align(
|
child: Align(
|
||||||
@@ -334,6 +318,7 @@ class _RegisterScreenState extends State<RegisterScreen> {
|
|||||||
.registerUser(
|
.registerUser(
|
||||||
controller.email.text.trim(),
|
controller.email.text.trim(),
|
||||||
controller.password.text.trim(),
|
controller.password.text.trim(),
|
||||||
|
controller.name.text.trim(),
|
||||||
)
|
)
|
||||||
.then((value) =>
|
.then((value) =>
|
||||||
Provider.of<UserProvider>(context, listen: false)
|
Provider.of<UserProvider>(context, listen: false)
|
||||||
@@ -445,6 +430,46 @@ class _RegisterScreenState extends State<RegisterScreen> {
|
|||||||
key: _formKey,
|
key: _formKey,
|
||||||
child: Column(
|
child: Column(
|
||||||
children: [
|
children: [
|
||||||
|
const Padding(
|
||||||
|
padding: EdgeInsets.only(bottom: 5),
|
||||||
|
child: Align(
|
||||||
|
alignment: Alignment.topLeft,
|
||||||
|
child: Text('Nombre',
|
||||||
|
style: TextStyle(
|
||||||
|
fontSize: 18.0,
|
||||||
|
color: Color(0xFF65676B))),
|
||||||
|
)),
|
||||||
|
Padding(
|
||||||
|
padding: const EdgeInsets.only(bottom: 18),
|
||||||
|
child: TextFormField(
|
||||||
|
controller: controller.name,
|
||||||
|
validator: (String? value) {
|
||||||
|
if (value == null || value.isEmpty) {
|
||||||
|
return 'Por favor ingresa tu nombre';
|
||||||
|
}
|
||||||
|
return null;
|
||||||
|
},
|
||||||
|
decoration: const InputDecoration(
|
||||||
|
border: OutlineInputBorder(
|
||||||
|
borderSide: BorderSide(color: Color(0xFFECECEC)),
|
||||||
|
borderRadius: BorderRadius.all(Radius.circular(50))),
|
||||||
|
enabledBorder: OutlineInputBorder(
|
||||||
|
borderSide: BorderSide(color: Color(0xFFECECEC)),
|
||||||
|
borderRadius: BorderRadius.all(Radius.circular(50))),
|
||||||
|
focusedBorder: OutlineInputBorder(
|
||||||
|
borderSide: BorderSide(color: Color(0xFFECECEC)),
|
||||||
|
borderRadius: BorderRadius.all(Radius.circular(50))),
|
||||||
|
errorBorder: OutlineInputBorder(
|
||||||
|
borderSide: BorderSide(color: Color.fromARGB(255, 184, 0, 0)),
|
||||||
|
borderRadius: BorderRadius.all(Radius.circular(50))),
|
||||||
|
hintText: 'Tu nombre completo',
|
||||||
|
fillColor: Color.fromARGB(255, 239, 239, 239),
|
||||||
|
filled: true,
|
||||||
|
prefixIcon: Icon(Icons.person_outline),
|
||||||
|
hintStyle: TextStyle(color: Colors.grey),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
const Padding(
|
const Padding(
|
||||||
padding: EdgeInsets.only(bottom: 5),
|
padding: EdgeInsets.only(bottom: 5),
|
||||||
child: Align(
|
child: Align(
|
||||||
@@ -639,6 +664,7 @@ class _RegisterScreenState extends State<RegisterScreen> {
|
|||||||
.registerUser(
|
.registerUser(
|
||||||
controller.email.text.trim(),
|
controller.email.text.trim(),
|
||||||
controller.password.text.trim(),
|
controller.password.text.trim(),
|
||||||
|
controller.name.text.trim(),
|
||||||
)
|
)
|
||||||
.then((value) =>
|
.then((value) =>
|
||||||
Provider.of<UserProvider>(context,
|
Provider.of<UserProvider>(context,
|
||||||
|
|||||||
@@ -1,4 +1,3 @@
|
|||||||
import 'package:firebase_auth/firebase_auth.dart';
|
|
||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
import 'package:get/get.dart';
|
import 'package:get/get.dart';
|
||||||
import 'package:prosappco/src/components/pop_appbar.dart';
|
import 'package:prosappco/src/components/pop_appbar.dart';
|
||||||
@@ -96,24 +95,14 @@ class ResetPasswordScreen extends StatelessWidget {
|
|||||||
),
|
),
|
||||||
const SizedBox(height: 80),
|
const SizedBox(height: 80),
|
||||||
PrimaryButtom(
|
PrimaryButtom(
|
||||||
onPressed: () async {
|
onPressed: () {
|
||||||
try {
|
ScaffoldMessenger.of(context).showSnackBar(
|
||||||
await FirebaseAuth.instance.sendPasswordResetEmail(
|
const SnackBar(
|
||||||
email: controller.email.text.trim());
|
|
||||||
ScaffoldMessenger.of(context).showSnackBar(
|
|
||||||
const SnackBar(
|
|
||||||
content: Text(
|
|
||||||
'Se ha enviado un enlace de restablecimiento de contraseña a tu correo electrónico.'),
|
|
||||||
),
|
|
||||||
);
|
|
||||||
Navigator.pop(context);
|
|
||||||
} catch (e) {
|
|
||||||
ScaffoldMessenger.of(context)
|
|
||||||
.showSnackBar(const SnackBar(
|
|
||||||
content: Text(
|
content: Text(
|
||||||
'Hubo un error al enviar el enlace de restablecimiento de contraseña.'),
|
'Funcionalidad no disponible. Contacta a soporte.'),
|
||||||
));
|
),
|
||||||
}
|
);
|
||||||
|
Navigator.pop(context);
|
||||||
},
|
},
|
||||||
label: 'Enviar'),
|
label: 'Enviar'),
|
||||||
],
|
],
|
||||||
|
|||||||
@@ -1,12 +1,12 @@
|
|||||||
import 'package:cloud_firestore/cloud_firestore.dart';
|
|
||||||
import 'package:firebase_storage/firebase_storage.dart';
|
|
||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
import 'package:flutter_rating_bar/flutter_rating_bar.dart';
|
import 'package:flutter_rating_bar/flutter_rating_bar.dart';
|
||||||
import 'package:intl/intl.dart';
|
import 'package:intl/intl.dart';
|
||||||
|
import 'package:prosappco/src/authentication/authentication_repository.dart';
|
||||||
import 'package:prosappco/src/components/photo_view.dart';
|
import 'package:prosappco/src/components/photo_view.dart';
|
||||||
import 'package:prosappco/src/components/pop_appbar.dart';
|
import 'package:prosappco/src/components/pop_appbar.dart';
|
||||||
import 'package:prosappco/src/models/event_model.dart';
|
import 'package:prosappco/src/models/event_model.dart';
|
||||||
import 'package:prosappco/src/models/user_model.dart';
|
import 'package:prosappco/src/models/user_model.dart';
|
||||||
|
import 'package:prosappco/src/services/api_service.dart';
|
||||||
|
|
||||||
class ScoreScreen extends StatefulWidget {
|
class ScoreScreen extends StatefulWidget {
|
||||||
final Event evento;
|
final Event evento;
|
||||||
@@ -19,32 +19,59 @@ class ScoreScreen extends StatefulWidget {
|
|||||||
|
|
||||||
class ScoreScreenState extends State<ScoreScreen> {
|
class ScoreScreenState extends State<ScoreScreen> {
|
||||||
TextEditingController commentController = TextEditingController();
|
TextEditingController commentController = TextEditingController();
|
||||||
Reference? ref_photo;
|
String? photoUrl;
|
||||||
String nombre = '';
|
String nombre = '';
|
||||||
double _rating = 1.0;
|
double _rating = 1.0;
|
||||||
|
final myUid = AuthenticationRepository.instance.getCurrentUserUid();
|
||||||
|
|
||||||
|
@override
|
||||||
|
void initState() {
|
||||||
|
super.initState();
|
||||||
|
_loadUserInfo();
|
||||||
|
}
|
||||||
|
|
||||||
|
Future<void> _loadUserInfo() async {
|
||||||
|
final targetId = (myUid != widget.evento.userId)
|
||||||
|
? widget.evento.userId
|
||||||
|
: widget.evento.professionalId;
|
||||||
|
try {
|
||||||
|
final Map<String, dynamic> data =
|
||||||
|
await ApiService.instance.get('/users/$targetId');
|
||||||
|
if (mounted) {
|
||||||
|
setState(() {
|
||||||
|
nombre = data['name'] ?? '';
|
||||||
|
photoUrl = data['picture'];
|
||||||
|
});
|
||||||
|
}
|
||||||
|
} catch (e) {
|
||||||
|
print('Error loading user for score: $e');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Future<void> _submit() async {
|
||||||
|
try {
|
||||||
|
final bool isPro = widget.pro;
|
||||||
|
// Update the service scored flag
|
||||||
|
await ApiService.instance.patch('/services/${widget.evento.id}', {
|
||||||
|
if (isPro) 'professional_scored': true,
|
||||||
|
if (!isPro) 'user_scored': true,
|
||||||
|
});
|
||||||
|
// Post the score/comment
|
||||||
|
await ApiService.instance.post('/comments', {
|
||||||
|
'comment': commentController.text,
|
||||||
|
'from_user': isPro ? widget.evento.professionalId : widget.evento.userId,
|
||||||
|
'is_from_professional': !isPro,
|
||||||
|
'score': _rating,
|
||||||
|
'to_user': isPro ? widget.evento.userId : widget.evento.professionalId,
|
||||||
|
});
|
||||||
|
if (mounted) Navigator.pop(context);
|
||||||
|
} catch (e) {
|
||||||
|
print('Error submitting score: $e');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
@override
|
@override
|
||||||
Widget build(BuildContext context) {
|
Widget build(BuildContext context) {
|
||||||
if (nombre == '') {
|
|
||||||
if (uid != widget.evento.userId) {
|
|
||||||
UserModel.getUser(widget.evento.userId).then((value) {
|
|
||||||
UserModel.getUser(uid.toString()).then((me) {
|
|
||||||
setState(() {
|
|
||||||
nombre = value.name;
|
|
||||||
ref_photo = value.photo;
|
|
||||||
});
|
|
||||||
});
|
|
||||||
});
|
|
||||||
} else {
|
|
||||||
UserModel.getUser(widget.evento.professionalId).then((value) {
|
|
||||||
setState(() {
|
|
||||||
nombre = value.name;
|
|
||||||
ref_photo = value.photo;
|
|
||||||
});
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return Scaffold(
|
return Scaffold(
|
||||||
appBar: PopAppbar(
|
appBar: PopAppbar(
|
||||||
onPressed: () {
|
onPressed: () {
|
||||||
@@ -59,7 +86,7 @@ class ScoreScreenState extends State<ScoreScreen> {
|
|||||||
leading: Padding(
|
leading: Padding(
|
||||||
padding: const EdgeInsets.symmetric(horizontal: 5),
|
padding: const EdgeInsets.symmetric(horizontal: 5),
|
||||||
child: ReferencePhoto(
|
child: ReferencePhoto(
|
||||||
ref: ref_photo,
|
ref: photoUrl,
|
||||||
size: 50,
|
size: 50,
|
||||||
sizeCircle: 50,
|
sizeCircle: 50,
|
||||||
sizeIcon: 35,
|
sizeIcon: 35,
|
||||||
@@ -119,60 +146,24 @@ class ScoreScreenState extends State<ScoreScreen> {
|
|||||||
),
|
),
|
||||||
Padding(
|
Padding(
|
||||||
padding: const EdgeInsets.only(bottom: 30),
|
padding: const EdgeInsets.only(bottom: 30),
|
||||||
child: Column(
|
child: ElevatedButton(
|
||||||
children: [
|
onPressed: _submit,
|
||||||
ElevatedButton(
|
style: ElevatedButton.styleFrom(
|
||||||
onPressed: () {
|
backgroundColor: const Color(0xFF2BA4EC),
|
||||||
if (widget.pro) {
|
shape: RoundedRectangleBorder(
|
||||||
FirebaseFirestore.instance
|
borderRadius: BorderRadius.circular(50),
|
||||||
.collection("services")
|
|
||||||
.doc(widget.evento.id)
|
|
||||||
.update({'professional_scored': true}).then((value) {
|
|
||||||
FirebaseFirestore.instance.collection("scores").add({
|
|
||||||
"comment": commentController.text,
|
|
||||||
"from_user": widget.evento.professionalId,
|
|
||||||
"is_from_professional": false,
|
|
||||||
"score": _rating,
|
|
||||||
"to_user": widget.evento.userId,
|
|
||||||
}).then((value) {
|
|
||||||
Navigator.pop(context);
|
|
||||||
});
|
|
||||||
});
|
|
||||||
} else {
|
|
||||||
FirebaseFirestore.instance
|
|
||||||
.collection("services")
|
|
||||||
.doc(widget.evento.id)
|
|
||||||
.update({'user_scored': true}).then((value) {
|
|
||||||
FirebaseFirestore.instance.collection("scores").add({
|
|
||||||
"comment": commentController.text,
|
|
||||||
"from_user": widget.evento.userId,
|
|
||||||
"is_from_professional": true,
|
|
||||||
"score": _rating,
|
|
||||||
"to_user": widget.evento.professionalId,
|
|
||||||
}).then((value) {
|
|
||||||
Navigator.pop(context);
|
|
||||||
});
|
|
||||||
});
|
|
||||||
}
|
|
||||||
},
|
|
||||||
style: ElevatedButton.styleFrom(
|
|
||||||
backgroundColor: const Color(0xFF2BA4EC),
|
|
||||||
shape: RoundedRectangleBorder(
|
|
||||||
borderRadius: BorderRadius.circular(50),
|
|
||||||
),
|
|
||||||
elevation: 0,
|
|
||||||
minimumSize: const Size(230, 60),
|
|
||||||
),
|
|
||||||
child: const Text(
|
|
||||||
'Enviar',
|
|
||||||
style: TextStyle(
|
|
||||||
color: Colors.white,
|
|
||||||
fontWeight: FontWeight.bold,
|
|
||||||
fontSize: 18,
|
|
||||||
),
|
|
||||||
),
|
|
||||||
),
|
),
|
||||||
],
|
elevation: 0,
|
||||||
|
minimumSize: const Size(230, 60),
|
||||||
|
),
|
||||||
|
child: const Text(
|
||||||
|
'Enviar',
|
||||||
|
style: TextStyle(
|
||||||
|
color: Colors.white,
|
||||||
|
fontWeight: FontWeight.bold,
|
||||||
|
fontSize: 18,
|
||||||
|
),
|
||||||
|
),
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
],
|
],
|
||||||
|
|||||||
@@ -1,7 +1,4 @@
|
|||||||
import 'dart:convert';
|
import 'dart:convert';
|
||||||
import 'package:cloud_firestore/cloud_firestore.dart';
|
|
||||||
import 'package:firebase_messaging/firebase_messaging.dart';
|
|
||||||
import 'package:firebase_storage/firebase_storage.dart';
|
|
||||||
import 'package:flutter/cupertino.dart';
|
import 'package:flutter/cupertino.dart';
|
||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
import 'package:geocoding/geocoding.dart';
|
import 'package:geocoding/geocoding.dart';
|
||||||
@@ -17,7 +14,6 @@ import 'package:prosappco/src/presentation/screens/professional.dart';
|
|||||||
import 'package:intl/intl.dart';
|
import 'package:intl/intl.dart';
|
||||||
import 'package:prosappco/src/presentation/screens/service_after.dart';
|
import 'package:prosappco/src/presentation/screens/service_after.dart';
|
||||||
import 'package:prosappco/src/presentation/screens/ubicacion.dart';
|
import 'package:prosappco/src/presentation/screens/ubicacion.dart';
|
||||||
import 'package:http/http.dart' as http;
|
|
||||||
import 'package:prosappco/src/components/network_utility.dart';
|
import 'package:prosappco/src/components/network_utility.dart';
|
||||||
import 'package:prosappco/src/presentation/widgets/shared/warning_snackbar.dart';
|
import 'package:prosappco/src/presentation/widgets/shared/warning_snackbar.dart';
|
||||||
import 'package:prosappco/src/providers/user_provider.dart';
|
import 'package:prosappco/src/providers/user_provider.dart';
|
||||||
@@ -31,54 +27,6 @@ class ServiceWebScreen extends StatefulWidget {
|
|||||||
}
|
}
|
||||||
|
|
||||||
class _ServiceOldScreenState extends State<ServiceWebScreen> {
|
class _ServiceOldScreenState extends State<ServiceWebScreen> {
|
||||||
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<void> sendPushNotification(String pro) async {
|
|
||||||
try {
|
|
||||||
http.Response response = await http.post(
|
|
||||||
Uri.parse('https://fcm.googleapis.com/fcm/send'),
|
|
||||||
headers: <String, String>{
|
|
||||||
'Content-Type': 'application/json; charset=UTF-8',
|
|
||||||
'Authorization':
|
|
||||||
'key=AAAAORdR-xU:APA91bF_wblg86jHAC-uexrXPHavYRlk5wge1Gf46m56V4J2D2L37Cp_hf46JZUzpvsWPSpqc5ewHelKI9LTifUG_s2mciMI6e5VLKo7E1R8btbNo7iaM9do2ctoyHKUm1atlZBdKaN2',
|
|
||||||
},
|
|
||||||
body: jsonEncode(
|
|
||||||
<String, dynamic>{
|
|
||||||
'notification': <String, dynamic>{
|
|
||||||
'body': 'alguien a solicitado tus servicios',
|
|
||||||
'title': 'Nueva solicitud',
|
|
||||||
},
|
|
||||||
'priority': 'high',
|
|
||||||
'data': <String, dynamic>{
|
|
||||||
'click_action': 'FLUTTER_NOTIFICATION_CLICK',
|
|
||||||
'id': '1',
|
|
||||||
'status': 'done',
|
|
||||||
'screen': 'solicitud'
|
|
||||||
},
|
|
||||||
'to': pro
|
|
||||||
},
|
|
||||||
),
|
|
||||||
);
|
|
||||||
|
|
||||||
response;
|
|
||||||
} catch (e) {
|
|
||||||
print('error al enviar notificacion $e');
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
EventoService eventoService = EventoService();
|
EventoService eventoService = EventoService();
|
||||||
String ubicacion = '';
|
String ubicacion = '';
|
||||||
final TextEditingController _locationController = TextEditingController();
|
final TextEditingController _locationController = TextEditingController();
|
||||||
@@ -97,43 +45,38 @@ class _ServiceOldScreenState extends State<ServiceWebScreen> {
|
|||||||
final DateFormat formatter = DateFormat('dd/MM/yyyy');
|
final DateFormat formatter = DateFormat('dd/MM/yyyy');
|
||||||
final DateTime now = DateTime.now();
|
final DateTime now = DateTime.now();
|
||||||
List<dynamic> _placesList = [];
|
List<dynamic> _placesList = [];
|
||||||
String selectedPlace = '';
|
|
||||||
String _coordsOfCity = '0.0,0.0';
|
String _coordsOfCity = '0.0,0.0';
|
||||||
|
|
||||||
DateTime? _selectedDate;
|
DateTime? _selectedDate;
|
||||||
TimeOfDay? _selectedTime;
|
TimeOfDay? _selectedTime;
|
||||||
|
|
||||||
GoogleMapController? googleMapController;
|
GoogleMapController? googleMapController;
|
||||||
|
|
||||||
Set<Marker> markers = {};
|
Set<Marker> markers = {};
|
||||||
|
|
||||||
|
final uid = AuthenticationRepository.instance.getCurrentUserUid();
|
||||||
|
|
||||||
|
BitmapDescriptor? _markerIcon;
|
||||||
|
String _ciudad = '...';
|
||||||
|
|
||||||
Future<Position> _determinePosition() async {
|
Future<Position> _determinePosition() async {
|
||||||
bool serviceEnabled;
|
bool serviceEnabled;
|
||||||
LocationPermission permission;
|
LocationPermission permission;
|
||||||
|
|
||||||
serviceEnabled = await Geolocator.isLocationServiceEnabled();
|
serviceEnabled = await Geolocator.isLocationServiceEnabled();
|
||||||
|
if (!serviceEnabled) return Future.error('Location services are disabled');
|
||||||
if (!serviceEnabled) {
|
|
||||||
return Future.error('Location services are disabled');
|
|
||||||
}
|
|
||||||
|
|
||||||
permission = await Geolocator.checkPermission();
|
permission = await Geolocator.checkPermission();
|
||||||
|
|
||||||
if (permission == LocationPermission.denied) {
|
if (permission == LocationPermission.denied) {
|
||||||
permission = await Geolocator.requestPermission();
|
permission = await Geolocator.requestPermission();
|
||||||
|
|
||||||
if (permission == LocationPermission.denied) {
|
if (permission == LocationPermission.denied) {
|
||||||
return Future.error('Location permission denied');
|
return Future.error('Location permission denied');
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (permission == LocationPermission.deniedForever) {
|
if (permission == LocationPermission.deniedForever) {
|
||||||
return Future.error('Location permissions are permanently denied');
|
return Future.error('Location permissions are permanently denied');
|
||||||
}
|
}
|
||||||
|
|
||||||
Position position = await Geolocator.getCurrentPosition();
|
return Geolocator.getCurrentPosition();
|
||||||
|
|
||||||
return position;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
Future<void> _selectDate(BuildContext context) async {
|
Future<void> _selectDate(BuildContext context) async {
|
||||||
@@ -142,17 +85,10 @@ class _ServiceOldScreenState extends State<ServiceWebScreen> {
|
|||||||
initialDate: now,
|
initialDate: now,
|
||||||
firstDate: now,
|
firstDate: now,
|
||||||
lastDate: DateTime(now.year + 1),
|
lastDate: DateTime(now.year + 1),
|
||||||
// builder: (context, child) {
|
|
||||||
// return Theme(data: ThemeData.dark(), child: child!);
|
|
||||||
// },
|
|
||||||
);
|
);
|
||||||
|
|
||||||
if (picked != null && picked != _selectedDate) {
|
if (picked != null && picked != _selectedDate) {
|
||||||
if (mounted) {
|
if (mounted) setState(() => _selectedDate = picked);
|
||||||
setState(() {
|
|
||||||
_selectedDate = picked;
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -161,73 +97,31 @@ class _ServiceOldScreenState extends State<ServiceWebScreen> {
|
|||||||
context: context,
|
context: context,
|
||||||
initialTime: TimeOfDay.now(),
|
initialTime: TimeOfDay.now(),
|
||||||
);
|
);
|
||||||
|
|
||||||
if (pickedTime != null) {
|
if (pickedTime != null) {
|
||||||
if (mounted) {
|
if (mounted) setState(() => _selectedTime = pickedTime);
|
||||||
setState(() {
|
|
||||||
_selectedTime = pickedTime;
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
Future<List<DocumentSnapshot<Map<String, dynamic>>>> getUsersWithActiveStatus(
|
|
||||||
String _serviceType) async {
|
|
||||||
var 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;
|
|
||||||
}
|
|
||||||
|
|
||||||
final FirebaseStorage storage = FirebaseStorage.instance;
|
|
||||||
final uid = AuthenticationRepository.instance.getCurrentUserUid();
|
|
||||||
final fcmToken = FirebaseMessaging.instance.getToken();
|
|
||||||
|
|
||||||
BitmapDescriptor? _markerIcon;
|
|
||||||
|
|
||||||
String _ciudad = '...';
|
|
||||||
|
|
||||||
void _setInitialCameraPosition(String coordsOfCity) async {
|
void _setInitialCameraPosition(String coordsOfCity) async {
|
||||||
List<String> coords = coordsOfCity.split(',');
|
List<String> coords = coordsOfCity.split(',');
|
||||||
double lat = double.parse(coords[0]);
|
double lat = double.parse(coords[0]);
|
||||||
double lng = double.parse(coords[1]);
|
double lng = double.parse(coords[1]);
|
||||||
|
|
||||||
googleMapController?.moveCamera(
|
googleMapController?.moveCamera(
|
||||||
CameraUpdate.newLatLngZoom(
|
CameraUpdate.newLatLngZoom(LatLng(lat, lng), 14.4746),
|
||||||
LatLng(lat, lng),
|
|
||||||
14.4746,
|
|
||||||
),
|
|
||||||
);
|
);
|
||||||
|
|
||||||
try {
|
try {
|
||||||
Position position = await _determinePosition();
|
Position position = await _determinePosition();
|
||||||
|
|
||||||
googleMapController?.animateCamera(
|
googleMapController?.animateCamera(
|
||||||
CameraUpdate.newCameraPosition(
|
CameraUpdate.newCameraPosition(
|
||||||
CameraPosition(
|
CameraPosition(
|
||||||
target: LatLng(
|
target: LatLng(position.latitude, position.longitude),
|
||||||
position.latitude,
|
|
||||||
position.longitude,
|
|
||||||
),
|
|
||||||
zoom: 17,
|
zoom: 17,
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
if (mounted) {
|
if (mounted) setState(() {});
|
||||||
setState(() {});
|
|
||||||
}
|
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
print('Error: $e');
|
print('Error: $e');
|
||||||
}
|
}
|
||||||
@@ -240,90 +134,22 @@ class _ServiceOldScreenState extends State<ServiceWebScreen> {
|
|||||||
_profesionalController.dispose();
|
_profesionalController.dispose();
|
||||||
_serviceTypeController.dispose();
|
_serviceTypeController.dispose();
|
||||||
_observacionController.dispose();
|
_observacionController.dispose();
|
||||||
|
|
||||||
super.dispose();
|
super.dispose();
|
||||||
}
|
}
|
||||||
|
|
||||||
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');
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
String? settings;
|
|
||||||
|
|
||||||
@override
|
@override
|
||||||
void initState() {
|
void initState() {
|
||||||
super.initState();
|
super.initState();
|
||||||
|
|
||||||
if (_ciudad == '...') {
|
// Load city from in-memory user
|
||||||
AuthenticationRepository.instance
|
final currentUser = AuthenticationRepository.instance.currentUser.value;
|
||||||
.getCity(uid.toString())
|
_ciudad = currentUser?.city ?? '...';
|
||||||
.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;
|
|
||||||
_setInitialCameraPosition(_coordsOfCity);
|
|
||||||
});
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
_saveToken();
|
|
||||||
|
|
||||||
BitmapDescriptor.fromAssetImage(
|
BitmapDescriptor.fromAssetImage(
|
||||||
const ImageConfiguration(size: Size(6, 6)), 'images/pro_marke.png')
|
const ImageConfiguration(size: Size(6, 6)), 'images/pro_marke.png')
|
||||||
.then((icon) {
|
.then((icon) {
|
||||||
if (mounted) {
|
if (mounted) setState(() => _markerIcon = icon);
|
||||||
setState(() {
|
|
||||||
_markerIcon = icon;
|
|
||||||
});
|
|
||||||
}
|
|
||||||
});
|
});
|
||||||
|
|
||||||
updateMarkersForServiceType(_serviceType);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
static CameraPosition initialCameraPosition = const CameraPosition(
|
static CameraPosition initialCameraPosition = const CameraPosition(
|
||||||
@@ -331,40 +157,18 @@ class _ServiceOldScreenState extends State<ServiceWebScreen> {
|
|||||||
zoom: 14.4746,
|
zoom: 14.4746,
|
||||||
);
|
);
|
||||||
|
|
||||||
void setInitialCameraPosition(String coordsOfCity) {
|
|
||||||
if (coordsOfCity != '0.0,0.0') {
|
|
||||||
List<String> coords = coordsOfCity.split(',');
|
|
||||||
double lat = double.parse(coords[0]);
|
|
||||||
double lng = double.parse(coords[1]);
|
|
||||||
|
|
||||||
initialCameraPosition = CameraPosition(
|
|
||||||
target: LatLng(lat, lng),
|
|
||||||
zoom: 14.4746,
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
late String lat;
|
|
||||||
late String long;
|
|
||||||
|
|
||||||
double latUser = 0.0;
|
double latUser = 0.0;
|
||||||
double lngUser = 0.0;
|
double lngUser = 0.0;
|
||||||
|
|
||||||
var coordinates;
|
var coordinates;
|
||||||
|
|
||||||
Future<String> getLocationName(double latitude, double longitude) async {
|
Future<String> getLocationName(double latitude, double longitude) async {
|
||||||
String address;
|
|
||||||
List<Placemark> placemarks =
|
List<Placemark> placemarks =
|
||||||
await placemarkFromCoordinates(latitude, longitude);
|
await placemarkFromCoordinates(latitude, longitude);
|
||||||
Placemark place = placemarks[0];
|
Placemark place = placemarks[0];
|
||||||
|
|
||||||
if (place.thoroughfare != '' || place.subThoroughfare != '') {
|
if (place.thoroughfare != '' || place.subThoroughfare != '') {
|
||||||
address =
|
return "${place.thoroughfare} ${place.subThoroughfare} ${place.subLocality}, ${place.locality}, ${place.administrativeArea}";
|
||||||
"${place.thoroughfare} ${place.subThoroughfare} ${place.subLocality}, ${place.locality}, ${place.administrativeArea}";
|
|
||||||
} else {
|
|
||||||
address = '';
|
|
||||||
}
|
}
|
||||||
return address;
|
return '';
|
||||||
}
|
}
|
||||||
|
|
||||||
void placeAutoComplete(String query) async {
|
void placeAutoComplete(String query) async {
|
||||||
@@ -374,7 +178,6 @@ class _ServiceOldScreenState extends State<ServiceWebScreen> {
|
|||||||
});
|
});
|
||||||
|
|
||||||
String? response = await NetworkUtility.fetchUrl(uri);
|
String? response = await NetworkUtility.fetchUrl(uri);
|
||||||
|
|
||||||
if (response != null) {
|
if (response != null) {
|
||||||
if (mounted) {
|
if (mounted) {
|
||||||
setState(() {
|
setState(() {
|
||||||
@@ -396,10 +199,7 @@ class _ServiceOldScreenState extends State<ServiceWebScreen> {
|
|||||||
elevation: 0,
|
elevation: 0,
|
||||||
title: const Text(
|
title: const Text(
|
||||||
'Prosapp',
|
'Prosapp',
|
||||||
style: TextStyle(
|
style: TextStyle(color: Colors.white, fontSize: 20),
|
||||||
color: Colors.white,
|
|
||||||
fontSize: 20,
|
|
||||||
),
|
|
||||||
)),
|
)),
|
||||||
body: Center(
|
body: Center(
|
||||||
child: SizedBox(
|
child: SizedBox(
|
||||||
@@ -443,9 +243,9 @@ class _ServiceOldScreenState extends State<ServiceWebScreen> {
|
|||||||
professionalTarifa = profesional.tarifa ?? 0;
|
professionalTarifa = profesional.tarifa ?? 0;
|
||||||
professionalUbicacion = profesional.ubicacion;
|
professionalUbicacion = profesional.ubicacion;
|
||||||
professionalAddress = _ubicationController.text;
|
professionalAddress = _ubicationController.text;
|
||||||
if (profesional.token != '') {
|
if (profesional.token != null &&
|
||||||
|
profesional.token != '') {
|
||||||
professionalToken = profesional.token!;
|
professionalToken = profesional.token!;
|
||||||
print(professionalToken);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if (ubicacion == 'sitio') {
|
if (ubicacion == 'sitio') {
|
||||||
@@ -583,44 +383,38 @@ class _ServiceOldScreenState extends State<ServiceWebScreen> {
|
|||||||
DateTime time2 =
|
DateTime time2 =
|
||||||
combinedDate.add(const Duration(hours: 2));
|
combinedDate.add(const Duration(hours: 2));
|
||||||
|
|
||||||
UserModel.getUser(uid.toString()).then((value) {
|
eventoService
|
||||||
eventoService
|
.createEvent(
|
||||||
.createEvent(
|
user!.name,
|
||||||
value.name,
|
_observacionController.text,
|
||||||
_observacionController.text,
|
'$_selectedDate',
|
||||||
'$_selectedDate',
|
'$combinedDate',
|
||||||
'$combinedDate',
|
'$time2',
|
||||||
'$time2',
|
professionalId,
|
||||||
professionalId,
|
ubicacion,
|
||||||
ubicacion,
|
professionalAddress,
|
||||||
professionalAddress,
|
latUser,
|
||||||
latUser,
|
lngUser,
|
||||||
lngUser,
|
'pendiente',
|
||||||
'pendiente',
|
professionalTarifa == 0 ? 0 : professionalTarifa,
|
||||||
professionalTarifa == 0 ? 0 : professionalTarifa,
|
false,
|
||||||
false,
|
false,
|
||||||
false,
|
)
|
||||||
)
|
.then((value) {
|
||||||
.then((value) {
|
Navigator.pushReplacement(
|
||||||
if (professionalToken != '') {
|
context,
|
||||||
sendPushNotification(professionalToken);
|
CupertinoPageRoute(
|
||||||
}
|
builder: (BuildContext context) {
|
||||||
Navigator.pushReplacement(
|
return ServiceAfterScreen(eventoId: value);
|
||||||
context,
|
},
|
||||||
CupertinoPageRoute(
|
),
|
||||||
builder: (BuildContext context) {
|
);
|
||||||
return ServiceAfterScreen(
|
|
||||||
eventoId: value,
|
|
||||||
);
|
|
||||||
},
|
|
||||||
),
|
|
||||||
);
|
|
||||||
});
|
|
||||||
});
|
});
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
WarningSnackbar.show(
|
WarningSnackbar.show(
|
||||||
title: 'Llena todos los campos',
|
title: 'Llena todos los campos',
|
||||||
message: 'Asegurate de llenar todos los campos.',
|
message:
|
||||||
|
'Asegurate de llenar todos los campos.',
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,17 +1,56 @@
|
|||||||
import 'package:cloud_firestore/cloud_firestore.dart';
|
|
||||||
import 'package:flutter/cupertino.dart';
|
import 'package:flutter/cupertino.dart';
|
||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
import 'package:flutter_rating_bar/flutter_rating_bar.dart';
|
import 'package:flutter_rating_bar/flutter_rating_bar.dart';
|
||||||
import 'package:intl/intl.dart';
|
import 'package:intl/intl.dart';
|
||||||
|
import 'package:prosappco/src/authentication/authentication_repository.dart';
|
||||||
import 'package:prosappco/src/components/drawer_professional.dart';
|
import 'package:prosappco/src/components/drawer_professional.dart';
|
||||||
import 'package:prosappco/src/models/event_model.dart';
|
import 'package:prosappco/src/models/event_model.dart';
|
||||||
import 'package:prosappco/src/models/scores_model.dart';
|
import 'package:prosappco/src/models/scores_model.dart';
|
||||||
import 'package:prosappco/src/presentation/screens/cita.dart';
|
import 'package:prosappco/src/presentation/screens/cita.dart';
|
||||||
|
import 'package:prosappco/src/services/api_service.dart';
|
||||||
|
|
||||||
class SolicitudScreen extends StatelessWidget {
|
class SolicitudScreen extends StatefulWidget {
|
||||||
SolicitudScreen({super.key});
|
SolicitudScreen({super.key});
|
||||||
|
|
||||||
DateTime today = DateTime.now();
|
@override
|
||||||
|
State<SolicitudScreen> createState() => _SolicitudScreenState();
|
||||||
|
}
|
||||||
|
|
||||||
|
class _SolicitudScreenState extends State<SolicitudScreen> {
|
||||||
|
List<Event> eventos = [];
|
||||||
|
bool loading = true;
|
||||||
|
final myUid = AuthenticationRepository.instance.getCurrentUserUid();
|
||||||
|
|
||||||
|
@override
|
||||||
|
void initState() {
|
||||||
|
super.initState();
|
||||||
|
_load();
|
||||||
|
}
|
||||||
|
|
||||||
|
Future<void> _load() async {
|
||||||
|
try {
|
||||||
|
final List<dynamic> data = await ApiService.instance
|
||||||
|
.get('/services?professionalId=$myUid&status=pendiente');
|
||||||
|
final List<Event> loaded = [];
|
||||||
|
for (final e in data) {
|
||||||
|
final event = Event.fromJson(e as Map<String, dynamic>);
|
||||||
|
event.scoresModel =
|
||||||
|
await ScoresModel.scoreTo(event.userId, false, false);
|
||||||
|
loaded.add(event);
|
||||||
|
}
|
||||||
|
loaded.sort((a, b) {
|
||||||
|
if (a.timeStamp == null || b.timeStamp == null) return 0;
|
||||||
|
return a.timeStamp!.compareTo(b.timeStamp!);
|
||||||
|
});
|
||||||
|
if (mounted) setState(() {
|
||||||
|
eventos = loaded;
|
||||||
|
loading = false;
|
||||||
|
});
|
||||||
|
} catch (e) {
|
||||||
|
print('Error loading solicitudes: $e');
|
||||||
|
if (mounted) setState(() => loading = false);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
@override
|
@override
|
||||||
Widget build(BuildContext context) {
|
Widget build(BuildContext context) {
|
||||||
@@ -19,176 +58,103 @@ class SolicitudScreen extends StatelessWidget {
|
|||||||
child: Scaffold(
|
child: Scaffold(
|
||||||
appBar: AppBar(
|
appBar: AppBar(
|
||||||
backgroundColor: Colors.white,
|
backgroundColor: Colors.white,
|
||||||
iconTheme: const IconThemeData(
|
iconTheme: const IconThemeData(color: Colors.black),
|
||||||
color: Colors.black,
|
|
||||||
),
|
|
||||||
title: const Text(
|
title: const Text(
|
||||||
'Solicitudes',
|
'Solicitudes',
|
||||||
style: TextStyle(
|
style: TextStyle(color: Colors.black),
|
||||||
color: Colors.black,
|
|
||||||
),
|
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
drawer: DrawerProfessional(),
|
drawer: DrawerProfessional(),
|
||||||
body: SingleChildScrollView(
|
body: loading
|
||||||
child: Column(
|
? const Center(child: CircularProgressIndicator())
|
||||||
children: [
|
: SingleChildScrollView(
|
||||||
_eventList(),
|
child: Column(children: [_eventList(context)]),
|
||||||
],
|
),
|
||||||
),
|
|
||||||
),
|
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
Widget _eventList() {
|
Widget _eventList(BuildContext context) {
|
||||||
return StreamBuilder<List<Event>>(
|
if (eventos.isEmpty) {
|
||||||
stream: FirebaseFirestore.instance
|
return const Padding(
|
||||||
.collection('services')
|
padding: EdgeInsets.symmetric(vertical: 50),
|
||||||
.where('professional_id', isEqualTo: uid)
|
child: Center(child: Text('No tienes citas')),
|
||||||
.where('status', whereIn: ['pendiente', ''])
|
);
|
||||||
.snapshots()
|
}
|
||||||
.asyncMap((snapshot) async {
|
return Column(
|
||||||
try {
|
children: [
|
||||||
List<Event> eventos = [];
|
...eventos.map(
|
||||||
for (var element in snapshot.docs) {
|
(event) => ListTile(
|
||||||
final event = Event.fromJson(element.data());
|
onTap: () {
|
||||||
event.scoresModel =
|
Navigator.push(
|
||||||
await ScoresModel.scoreTo(event.userId, false, false);
|
context,
|
||||||
event.id = element.id;
|
CupertinoPageRoute(
|
||||||
eventos.add(event);
|
builder: (BuildContext context) {
|
||||||
}
|
return CitaScreen(evento: event);
|
||||||
return eventos;
|
},
|
||||||
} catch (e) {
|
|
||||||
print('Error getByProId $e');
|
|
||||||
return [];
|
|
||||||
}
|
|
||||||
}),
|
|
||||||
builder: (BuildContext context, AsyncSnapshot<List<Event>> snapshot) {
|
|
||||||
if (!snapshot.hasData) {
|
|
||||||
return const Center(
|
|
||||||
child: CircularProgressIndicator(),
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
List<Event> eventos = [];
|
|
||||||
|
|
||||||
try {
|
|
||||||
eventos.addAll(snapshot.data!);
|
|
||||||
eventos.sort((a, b) => a.timeStamp!.compareTo(b.timeStamp!));
|
|
||||||
} catch (e) {
|
|
||||||
print("Error" + e.toString());
|
|
||||||
}
|
|
||||||
|
|
||||||
if (eventos.isEmpty) {
|
|
||||||
return const Padding(
|
|
||||||
padding: EdgeInsets.symmetric(vertical: 50),
|
|
||||||
child: Center(child: Text('No tienes citas')),
|
|
||||||
);
|
|
||||||
}
|
|
||||||
return Column(
|
|
||||||
children: [
|
|
||||||
...eventos.map(
|
|
||||||
(event) => ListTile(
|
|
||||||
onTap: () {
|
|
||||||
Navigator.push(
|
|
||||||
context,
|
|
||||||
CupertinoPageRoute(
|
|
||||||
builder: (BuildContext context) {
|
|
||||||
return CitaScreen(evento: event);
|
|
||||||
},
|
|
||||||
),
|
|
||||||
);
|
|
||||||
},
|
|
||||||
leading: Column(
|
|
||||||
mainAxisAlignment: MainAxisAlignment.center,
|
|
||||||
children: [
|
|
||||||
Text(DateFormat('h:mm a')
|
|
||||||
.format(DateTime.parse(event.range1Hour1))),
|
|
||||||
],
|
|
||||||
),
|
),
|
||||||
title: Column(
|
);
|
||||||
crossAxisAlignment: CrossAxisAlignment.start,
|
},
|
||||||
children: [
|
leading: Column(
|
||||||
Text(
|
mainAxisAlignment: MainAxisAlignment.center,
|
||||||
event.title,
|
children: [
|
||||||
style: const TextStyle(
|
Text(DateFormat('h:mm a')
|
||||||
color: Colors.black,
|
.format(DateTime.parse(event.range1Hour1))),
|
||||||
fontWeight: FontWeight.bold,
|
],
|
||||||
fontSize: 16,
|
|
||||||
),
|
|
||||||
),
|
|
||||||
Text(
|
|
||||||
'${DateFormat('dd MMMM', 'es').format(DateTime.parse(event.day))} - ${DateFormat('h:mm a').format(DateTime.parse(event.range1Hour1))}',
|
|
||||||
style: const TextStyle(
|
|
||||||
color: Colors.grey,
|
|
||||||
fontSize: 16,
|
|
||||||
),
|
|
||||||
)
|
|
||||||
],
|
|
||||||
),
|
|
||||||
|
|
||||||
// RichText(
|
|
||||||
// text: TextSpan(
|
|
||||||
// children: [
|
|
||||||
// TextSpan(
|
|
||||||
// text: '${event.title}, ',
|
|
||||||
// style: const TextStyle(
|
|
||||||
// color: Colors.black,
|
|
||||||
// fontWeight: FontWeight.bold,
|
|
||||||
// fontSize: 16,
|
|
||||||
// ),
|
|
||||||
// ),
|
|
||||||
// TextSpan(
|
|
||||||
// text: DateFormat('dd MMM', 'es')
|
|
||||||
// .format(DateTime.parse(event.day)),
|
|
||||||
// style: const TextStyle(
|
|
||||||
// color: Colors.grey,
|
|
||||||
// fontSize: 16,
|
|
||||||
// ),
|
|
||||||
// ),
|
|
||||||
// ],
|
|
||||||
// ),
|
|
||||||
// ),
|
|
||||||
subtitle: Column(
|
|
||||||
crossAxisAlignment: CrossAxisAlignment.start,
|
|
||||||
children: [
|
|
||||||
Row(
|
|
||||||
children: [
|
|
||||||
RatingBar.builder(
|
|
||||||
initialRating: event.scoresModel?.average ?? 0,
|
|
||||||
minRating: 1,
|
|
||||||
direction: Axis.horizontal,
|
|
||||||
allowHalfRating: true,
|
|
||||||
itemCount: 5,
|
|
||||||
itemSize: 25,
|
|
||||||
maxRating: 5,
|
|
||||||
itemPadding:
|
|
||||||
const EdgeInsets.symmetric(horizontal: 0),
|
|
||||||
itemBuilder: (context, _) => const Icon(
|
|
||||||
Icons.star,
|
|
||||||
color: Color(0xFF2BA4EC),
|
|
||||||
),
|
|
||||||
onRatingUpdate: (rating) {},
|
|
||||||
ignoreGestures: true,
|
|
||||||
),
|
|
||||||
const SizedBox(width: 5),
|
|
||||||
Text(
|
|
||||||
'(${event.scoresModel?.total.toString()}) ${event.scoresModel?.average.toStringAsFixed(1)}'),
|
|
||||||
],
|
|
||||||
),
|
|
||||||
Text(
|
|
||||||
'"${event.description}"',
|
|
||||||
style: const TextStyle(fontStyle: FontStyle.italic),
|
|
||||||
),
|
|
||||||
],
|
|
||||||
),
|
|
||||||
trailing: const Icon(Icons.keyboard_arrow_right),
|
|
||||||
),
|
|
||||||
),
|
),
|
||||||
],
|
title: Column(
|
||||||
);
|
crossAxisAlignment: CrossAxisAlignment.start,
|
||||||
},
|
children: [
|
||||||
|
Text(
|
||||||
|
event.title,
|
||||||
|
style: const TextStyle(
|
||||||
|
color: Colors.black,
|
||||||
|
fontWeight: FontWeight.bold,
|
||||||
|
fontSize: 16,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
Text(
|
||||||
|
'${DateFormat('dd MMMM', 'es').format(DateTime.parse(event.day))} - ${DateFormat('h:mm a').format(DateTime.parse(event.range1Hour1))}',
|
||||||
|
style: const TextStyle(color: Colors.grey, fontSize: 16),
|
||||||
|
)
|
||||||
|
],
|
||||||
|
),
|
||||||
|
subtitle: Column(
|
||||||
|
crossAxisAlignment: CrossAxisAlignment.start,
|
||||||
|
children: [
|
||||||
|
Row(
|
||||||
|
children: [
|
||||||
|
RatingBar.builder(
|
||||||
|
initialRating: event.scoresModel?.average ?? 0,
|
||||||
|
minRating: 1,
|
||||||
|
direction: Axis.horizontal,
|
||||||
|
allowHalfRating: true,
|
||||||
|
itemCount: 5,
|
||||||
|
itemSize: 25,
|
||||||
|
maxRating: 5,
|
||||||
|
itemPadding: const EdgeInsets.symmetric(horizontal: 0),
|
||||||
|
itemBuilder: (context, _) => const Icon(
|
||||||
|
Icons.star,
|
||||||
|
color: Color(0xFF2BA4EC),
|
||||||
|
),
|
||||||
|
onRatingUpdate: (rating) {},
|
||||||
|
ignoreGestures: true,
|
||||||
|
),
|
||||||
|
const SizedBox(width: 5),
|
||||||
|
Text(
|
||||||
|
'(${event.scoresModel?.total.toString()}) ${event.scoresModel?.average.toStringAsFixed(1)}'),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
Text(
|
||||||
|
'"${event.description}"',
|
||||||
|
style: const TextStyle(fontStyle: FontStyle.italic),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
trailing: const Icon(Icons.keyboard_arrow_right),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
],
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,54 +1,32 @@
|
|||||||
import 'package:cloud_firestore/cloud_firestore.dart';
|
|
||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
import 'package:firebase_auth/firebase_auth.dart';
|
|
||||||
import 'package:prosappco/src/models/scores_model.dart';
|
|
||||||
import 'package:prosappco/src/models/user_model.dart';
|
import 'package:prosappco/src/models/user_model.dart';
|
||||||
|
import 'package:prosappco/src/services/api_service.dart';
|
||||||
|
|
||||||
class UserProvider extends ChangeNotifier {
|
class UserProvider extends ChangeNotifier {
|
||||||
final FirebaseFirestore _firestore = FirebaseFirestore.instance;
|
final _api = ApiService.instance;
|
||||||
UserModel? _user;
|
UserModel? _user;
|
||||||
ScoresModel? _score;
|
|
||||||
Stream<DocumentSnapshot<Map<String, dynamic>>>? _stream;
|
|
||||||
|
|
||||||
UserProvider() {
|
UserProvider() {
|
||||||
initUserProvider();
|
loadUser();
|
||||||
}
|
}
|
||||||
|
|
||||||
void initUserProvider() {
|
Future<void> loadUser() async {
|
||||||
var uid = FirebaseAuth.instance.currentUser?.uid;
|
final token = await _api.getToken();
|
||||||
if (uid == null) return;
|
if (token == null) return;
|
||||||
_stream = _firestore.collection('users').doc(uid).snapshots();
|
try {
|
||||||
_stream?.listen((documentSnapshot) async {
|
final data = await _api.get('/auth/me');
|
||||||
if (documentSnapshot.exists) {
|
_user = UserModel.fromApi(data);
|
||||||
final data = documentSnapshot.data() as Map<String, dynamic>;
|
notifyListeners();
|
||||||
_user = UserModel.fromFirestore(data);
|
} catch (_) {}
|
||||||
_score = await ScoresModel.scoreTo(uid, false, false);
|
|
||||||
notifyListeners();
|
|
||||||
}
|
|
||||||
}, onDone: () {}, onError: (error) {});
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
Future<void> initUserProvider() => loadUser();
|
||||||
|
Future<void> updateUserDataAndScores() => loadUser();
|
||||||
|
|
||||||
void setNullUser() {
|
void setNullUser() {
|
||||||
_user = null;
|
_user = null;
|
||||||
_score = null;
|
|
||||||
_stream = null;
|
|
||||||
notifyListeners();
|
notifyListeners();
|
||||||
}
|
}
|
||||||
|
|
||||||
Future<void> updateUserDataAndScores() async {
|
|
||||||
var uid = FirebaseAuth.instance.currentUser?.uid;
|
|
||||||
if (uid == null) return;
|
|
||||||
|
|
||||||
var documentSnapshot = await _firestore.collection('users').doc(uid).get();
|
|
||||||
|
|
||||||
if (documentSnapshot.exists) {
|
|
||||||
final data = documentSnapshot.data() as Map<String, dynamic>;
|
|
||||||
_user = UserModel.fromFirestore(data);
|
|
||||||
_score = await ScoresModel.scoreTo(uid, false, false);
|
|
||||||
notifyListeners();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
UserModel? get user => _user;
|
UserModel? get user => _user;
|
||||||
ScoresModel? get score => _score;
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -0,0 +1,90 @@
|
|||||||
|
import 'dart:convert';
|
||||||
|
import 'package:http/http.dart' as http;
|
||||||
|
import 'package:shared_preferences/shared_preferences.dart';
|
||||||
|
|
||||||
|
const String _baseUrl = 'https://backend.prosapp.co/api/v1';
|
||||||
|
|
||||||
|
class ApiException implements Exception {
|
||||||
|
final int statusCode;
|
||||||
|
final String message;
|
||||||
|
ApiException(this.statusCode, this.message);
|
||||||
|
@override
|
||||||
|
String toString() => 'ApiException($statusCode): $message';
|
||||||
|
}
|
||||||
|
|
||||||
|
class ApiService {
|
||||||
|
static ApiService? _instance;
|
||||||
|
static ApiService get instance => _instance ??= ApiService._();
|
||||||
|
ApiService._();
|
||||||
|
|
||||||
|
String? _token;
|
||||||
|
|
||||||
|
Future<String?> getToken() async {
|
||||||
|
if (_token != null) return _token;
|
||||||
|
final prefs = await SharedPreferences.getInstance();
|
||||||
|
_token = prefs.getString('token');
|
||||||
|
return _token;
|
||||||
|
}
|
||||||
|
|
||||||
|
Future<void> saveToken(String token) async {
|
||||||
|
_token = token;
|
||||||
|
final prefs = await SharedPreferences.getInstance();
|
||||||
|
await prefs.setString('token', token);
|
||||||
|
}
|
||||||
|
|
||||||
|
Future<void> clearToken() async {
|
||||||
|
_token = null;
|
||||||
|
final prefs = await SharedPreferences.getInstance();
|
||||||
|
await prefs.remove('token');
|
||||||
|
}
|
||||||
|
|
||||||
|
Future<Map<String, String>> _headers({bool auth = true}) async {
|
||||||
|
final headers = {'Content-Type': 'application/json'};
|
||||||
|
if (auth) {
|
||||||
|
final token = await getToken();
|
||||||
|
if (token != null) headers['Authorization'] = 'Bearer $token';
|
||||||
|
}
|
||||||
|
return headers;
|
||||||
|
}
|
||||||
|
|
||||||
|
dynamic _parse(http.Response res) {
|
||||||
|
final body = jsonDecode(res.body);
|
||||||
|
if (res.statusCode >= 200 && res.statusCode < 300) return body;
|
||||||
|
final msg = body is Map ? (body['message'] ?? res.reasonPhrase) : res.reasonPhrase;
|
||||||
|
throw ApiException(res.statusCode, msg.toString());
|
||||||
|
}
|
||||||
|
|
||||||
|
Future<dynamic> get(String path, {bool auth = true}) async {
|
||||||
|
final res = await http.get(
|
||||||
|
Uri.parse('$_baseUrl$path'),
|
||||||
|
headers: await _headers(auth: auth),
|
||||||
|
);
|
||||||
|
return _parse(res);
|
||||||
|
}
|
||||||
|
|
||||||
|
Future<dynamic> post(String path, Map<String, dynamic> body, {bool auth = false}) async {
|
||||||
|
final res = await http.post(
|
||||||
|
Uri.parse('$_baseUrl$path'),
|
||||||
|
headers: await _headers(auth: auth),
|
||||||
|
body: jsonEncode(body),
|
||||||
|
);
|
||||||
|
return _parse(res);
|
||||||
|
}
|
||||||
|
|
||||||
|
Future<dynamic> patch(String path, Map<String, dynamic> body, {bool auth = true}) async {
|
||||||
|
final res = await http.patch(
|
||||||
|
Uri.parse('$_baseUrl$path'),
|
||||||
|
headers: await _headers(auth: auth),
|
||||||
|
body: jsonEncode(body),
|
||||||
|
);
|
||||||
|
return _parse(res);
|
||||||
|
}
|
||||||
|
|
||||||
|
Future<dynamic> delete(String path, {bool auth = true}) async {
|
||||||
|
final res = await http.delete(
|
||||||
|
Uri.parse('$_baseUrl$path'),
|
||||||
|
headers: await _headers(auth: auth),
|
||||||
|
);
|
||||||
|
return _parse(res);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -1,56 +1,6 @@
|
|||||||
import 'package:firebase_messaging/firebase_messaging.dart';
|
// ponytail: FCM not applicable on web; stub so imports compile
|
||||||
import 'package:get/get.dart';
|
|
||||||
|
|
||||||
class FirebaseMessagingService {
|
class FirebaseMessagingService {
|
||||||
FirebaseMessaging _firebaseMessaging = FirebaseMessaging.instance;
|
|
||||||
|
|
||||||
Future<void> initializeFirebaseMessaging() async {
|
Future<void> initializeFirebaseMessaging() async {
|
||||||
// Solicitar permisos de notificación si es necesario (opcional)
|
// No-op: push notifications handled server-side
|
||||||
NotificationSettings settings = await _firebaseMessaging.requestPermission(
|
|
||||||
alert: true,
|
|
||||||
badge: true,
|
|
||||||
sound: true,
|
|
||||||
);
|
|
||||||
|
|
||||||
// Verificar si los permisos de notificación están habilitados
|
|
||||||
if (settings.authorizationStatus == AuthorizationStatus.authorized ||
|
|
||||||
settings.authorizationStatus == AuthorizationStatus.provisional) {
|
|
||||||
// Obtener el token de registro para la instancia de la aplicación
|
|
||||||
String? token = await _firebaseMessaging.getToken();
|
|
||||||
print('Token FCM: $token');
|
|
||||||
|
|
||||||
FirebaseMessaging.onMessage.listen((RemoteMessage message) {
|
|
||||||
print(
|
|
||||||
'Mensaje FCM recibido: ${message.notification?.title} - ${message.notification?.body}');
|
|
||||||
|
|
||||||
// Obtener el valor de la clave "screen" de los datos de la notificación
|
|
||||||
String? notificationScreen = message.data['screen'];
|
|
||||||
|
|
||||||
// Navegar a la pantalla correspondiente según el valor de la clave "screen"
|
|
||||||
if (notificationScreen == "misservicios") {
|
|
||||||
// Navegar a MyServicesScreen
|
|
||||||
Get.offNamed('/misservicios');
|
|
||||||
} else if (notificationScreen == "solicitud") {
|
|
||||||
// Navegar a SolicitudScreen
|
|
||||||
Get.offNamed('/solicitud');
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
// Manejar la notificación cuando se toca y la aplicación está en primer plano (opcional)
|
|
||||||
FirebaseMessaging.onMessageOpenedApp.listen((RemoteMessage message) {
|
|
||||||
print(
|
|
||||||
'Mensaje FCM abierto desde la aplicación en primer plano: ${message.notification?.title} - ${message.notification?.body}');
|
|
||||||
// Aquí puedes redirigir al usuario a una pantalla específica o realizar acciones según los datos recibidos
|
|
||||||
});
|
|
||||||
|
|
||||||
// Manejar la notificación cuando se toca y la aplicación está cerrada (opcional)
|
|
||||||
RemoteMessage? initialMessage =
|
|
||||||
await FirebaseMessaging.instance.getInitialMessage();
|
|
||||||
if (initialMessage != null) {
|
|
||||||
print(
|
|
||||||
'Mensaje FCM abierto desde la aplicación cerrada: ${initialMessage.notification?.title} - ${initialMessage.notification?.body}');
|
|
||||||
// Aquí puedes redirigir al usuario a una pantalla específica o realizar acciones según los datos recibidos
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,5 +1,4 @@
|
|||||||
import 'package:flutter_local_notifications/flutter_local_notifications.dart';
|
import 'package:flutter_local_notifications/flutter_local_notifications.dart';
|
||||||
import 'package:prosappco/src/services/firebase_messaging.dart';
|
|
||||||
|
|
||||||
final FlutterLocalNotificationsPlugin flutterLocalNotificationsPlugin =
|
final FlutterLocalNotificationsPlugin flutterLocalNotificationsPlugin =
|
||||||
FlutterLocalNotificationsPlugin();
|
FlutterLocalNotificationsPlugin();
|
||||||
@@ -20,11 +19,6 @@ Future<void> initNotifications() async {
|
|||||||
);
|
);
|
||||||
|
|
||||||
await flutterLocalNotificationsPlugin.initialize(initializationSettings);
|
await flutterLocalNotificationsPlugin.initialize(initializationSettings);
|
||||||
|
|
||||||
// Inicializar Firebase Messaging
|
|
||||||
FirebaseMessagingService firebaseMessagingService =
|
|
||||||
FirebaseMessagingService();
|
|
||||||
await firebaseMessagingService.initializeFirebaseMessaging();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
Future<void> showNotification(String title, String body) async {
|
Future<void> showNotification(String title, String body) async {
|
||||||
|
|||||||
@@ -38,17 +38,13 @@ dependencies:
|
|||||||
location: ^5.0.3
|
location: ^5.0.3
|
||||||
flutter_polyline_points: ^2.0.0
|
flutter_polyline_points: ^2.0.0
|
||||||
google_maps_flutter: ^2.5.0
|
google_maps_flutter: ^2.5.0
|
||||||
firebase_auth: ^4.6.2
|
|
||||||
firebase_core: ^2.13.1
|
|
||||||
file_picker: ^5.3.2
|
file_picker: ^5.3.2
|
||||||
google_sign_in: ^6.1.4
|
|
||||||
provider: ^6.0.5
|
provider: ^6.0.5
|
||||||
package_info_plus: ^4.2.0
|
package_info_plus: ^4.2.0
|
||||||
get:
|
get:
|
||||||
font_awesome_flutter: ^10.4.0
|
font_awesome_flutter: ^10.4.0
|
||||||
flutter_otp_text_field:
|
flutter_otp_text_field:
|
||||||
intl_phone_field:
|
intl_phone_field:
|
||||||
cloud_firestore:
|
|
||||||
diacritic:
|
diacritic:
|
||||||
image_picker: ^1.0.4
|
image_picker: ^1.0.4
|
||||||
flutter_animate:
|
flutter_animate:
|
||||||
@@ -61,12 +57,10 @@ dependencies:
|
|||||||
community_material_icon: ^5.9.55
|
community_material_icon: ^5.9.55
|
||||||
flutter_email_sender: ^5.2.0
|
flutter_email_sender: ^5.2.0
|
||||||
webview_flutter: ^4.4.1
|
webview_flutter: ^4.4.1
|
||||||
firebase_storage: ^11.2.2
|
|
||||||
responsive_builder: ^0.7.0
|
responsive_builder: ^0.7.0
|
||||||
flutter_local_notifications: ^14.1.1
|
flutter_local_notifications: ^14.1.1
|
||||||
flutter_dialogs: ^3.0.0
|
flutter_dialogs: ^3.0.0
|
||||||
universal_html: ^2.2.3
|
universal_html: ^2.2.3
|
||||||
firebase_messaging: ^14.6.3
|
|
||||||
animated_splash_screen: ^1.3.0
|
animated_splash_screen: ^1.3.0
|
||||||
animate_do: ^3.0.2
|
animate_do: ^3.0.2
|
||||||
shimmer: ^3.0.0
|
shimmer: ^3.0.0
|
||||||
|
|||||||
Reference in New Issue
Block a user