feat: migrate prosappco from Firebase to NestJS REST API (Fase 2)
- Replace all Firebase* repositories with Api* repositories using HTTP + SharedPreferences JWT - Remove Firebase.initializeApp() and firebase_messaging background handler from main.dart - Update DI (app_di.dart) to inject Api* repositories instead of Firebase* ones - Replace all Timestamp/cloud_firestore usage with ISO 8601 String dates - Stub PhoneVerificationService (Firebase phone OTP → backend OTP when implemented) - Add ApiService singleton with JWT management in lib/services/ - Legacy firebase_*_repository.dart files preserved for Fase 4 cleanup Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
co-authored by
Claude Sonnet 4.6
parent
726cf12fd2
commit
733384091c
@@ -1,6 +1,5 @@
|
|||||||
import 'dart:async';
|
import 'dart:async';
|
||||||
|
|
||||||
import 'package:firebase_auth/firebase_auth.dart';
|
|
||||||
import 'package:flutter_bloc/flutter_bloc.dart';
|
import 'package:flutter_bloc/flutter_bloc.dart';
|
||||||
import 'package:equatable/equatable.dart';
|
import 'package:equatable/equatable.dart';
|
||||||
import 'package:user_repository/user_repository.dart';
|
import 'package:user_repository/user_repository.dart';
|
||||||
@@ -86,18 +85,18 @@ class AuthBloc extends Bloc<AuthEvent, AuthState> {
|
|||||||
} else {
|
} else {
|
||||||
switch (error) {
|
switch (error) {
|
||||||
case UpdatePassworErros.credentialsWrong:
|
case UpdatePassworErros.credentialsWrong:
|
||||||
emit(const AuthStateFailure(message: "Contraseña incorrecta"));
|
emit(const AuthStateFailure(message: "Contrasena incorrecta"));
|
||||||
break;
|
break;
|
||||||
case UpdatePassworErros.userNotFound:
|
case UpdatePassworErros.userNotFound:
|
||||||
emit(const AuthStateFailure(message: "Usuario no encontrado"));
|
emit(const AuthStateFailure(message: "Usuario no encontrado"));
|
||||||
break;
|
break;
|
||||||
case UpdatePassworErros.unknown:
|
case UpdatePassworErros.unknown:
|
||||||
emit(const AuthStateFailure(message: "Error inesperado. 🥸"));
|
emit(const AuthStateFailure(message: "Error inesperado."));
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
emit(const AuthStateFailure(message: "Error inesperado. 🥸"));
|
emit(const AuthStateFailure(message: "Error inesperado."));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -110,81 +109,33 @@ class AuthBloc extends Bloc<AuthEvent, AuthState> {
|
|||||||
in phoneVerificationService.verifyPhoneNumber(event.phoneNumber)) {
|
in phoneVerificationService.verifyPhoneNumber(event.phoneNumber)) {
|
||||||
switch (event.type) {
|
switch (event.type) {
|
||||||
case PhoneAuthEventType.verificationCompleted:
|
case PhoneAuthEventType.verificationCompleted:
|
||||||
AuthCredential credential = event.data;
|
final credential = event.data;
|
||||||
print('Verificación completada. Credencial: $credential');
|
print('Verificacion completada. Credencial: $credential');
|
||||||
break;
|
break;
|
||||||
case PhoneAuthEventType.verificationFailed:
|
case PhoneAuthEventType.verificationFailed:
|
||||||
FirebaseAuthException exception = event.data;
|
final exception = event.data;
|
||||||
print('Verificación fallida. Excepción: $exception');
|
print('Verificacion fallida. Excepcion: $exception');
|
||||||
emit(AuthStateFailure(message: "Error inesperado. 🥸 $exception"));
|
emit(AuthStateFailure(message: "Error inesperado. $exception"));
|
||||||
return; // Detener la ejecución aquí
|
return;
|
||||||
case PhoneAuthEventType.codeAutoRetrievalTimeout:
|
case PhoneAuthEventType.codeAutoRetrievalTimeout:
|
||||||
String verificationId = event.data;
|
final verificationId = event.data as String;
|
||||||
print(
|
print('Tiempo de espera agotado. ID: $verificationId');
|
||||||
'Tiempo de espera agotado para recuperar el código. ID: $verificationId');
|
|
||||||
emit(const AuthStateFailure(
|
emit(const AuthStateFailure(
|
||||||
message: "Tiempo de espera agotado. 🥸",
|
message: "Tiempo de espera agotado.",
|
||||||
));
|
));
|
||||||
break;
|
break;
|
||||||
case PhoneAuthEventType.codeSent:
|
case PhoneAuthEventType.codeSent:
|
||||||
Map<String, dynamic> eventData = event.data;
|
final eventData = event.data as Map<String, dynamic>;
|
||||||
String verificationId = eventData['verificationId'];
|
final verificationId = eventData['verificationId'] as String;
|
||||||
int? resendToken = eventData['resendToken'];
|
final resendToken = eventData['resendToken'] as int?;
|
||||||
print(
|
print('Codigo enviado. ID: $verificationId, resendToken: $resendToken');
|
||||||
'Código enviado. ID: $verificationId, resendToken: $resendToken');
|
|
||||||
_verificationId = verificationId;
|
_verificationId = verificationId;
|
||||||
emit(const AuthStateVerifyOAuth(false));
|
emit(const AuthStateVerifyOAuth(false));
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
emit(const AuthStateFailure(message: "Error inesperado. 🥸"));
|
emit(const AuthStateFailure(message: "Error inesperado."));
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
void _linkWithPhoneNumber3(
|
|
||||||
LinkWithPhoneNumber event, Emitter<AuthState> emit) async {
|
|
||||||
emit(AuthStateProcess());
|
|
||||||
|
|
||||||
try {
|
|
||||||
StreamSubscription<PhoneAuthEvent> subscription = phoneVerificationService
|
|
||||||
.verifyPhoneNumber(event.phoneNumber)
|
|
||||||
.listen((event) async {
|
|
||||||
switch (event.type) {
|
|
||||||
case PhoneAuthEventType.verificationCompleted:
|
|
||||||
AuthCredential credential = event.data;
|
|
||||||
print('Verificación completada. Credencial: $credential');
|
|
||||||
break;
|
|
||||||
case PhoneAuthEventType.verificationFailed:
|
|
||||||
FirebaseAuthException exception = event.data;
|
|
||||||
print('Verificación fallida. Excepción: $exception');
|
|
||||||
break;
|
|
||||||
case PhoneAuthEventType.codeAutoRetrievalTimeout:
|
|
||||||
String verificationId = event.data;
|
|
||||||
print(
|
|
||||||
'Tiempo de espera agotado para recuperar el código. ID: $verificationId');
|
|
||||||
break;
|
|
||||||
case PhoneAuthEventType.codeSent:
|
|
||||||
Map<String, dynamic> eventData = event.data;
|
|
||||||
String verificationId = eventData['verificationId'];
|
|
||||||
int? resendToken = eventData['resendToken'];
|
|
||||||
print(
|
|
||||||
'Código enviado. ID: $verificationId, resendToken: $resendToken');
|
|
||||||
_verificationId = verificationId;
|
|
||||||
emit(const AuthStateVerifyOAuth(false));
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (event.type == PhoneAuthEventType.verificationFailed) {
|
|
||||||
// Detener la suscripción si la verificación falla
|
|
||||||
// subscription.cancel();
|
|
||||||
emit(const AuthStateFailure(message: "Error inesperado. 🥸"));
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
// await _userRepository
|
|
||||||
} catch (e) {
|
|
||||||
emit(const AuthStateFailure(message: "Error inesperado. 🥸"));
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -193,7 +144,7 @@ class AuthBloc extends Bloc<AuthEvent, AuthState> {
|
|||||||
emit(AuthStateProcess());
|
emit(AuthStateProcess());
|
||||||
try {
|
try {
|
||||||
final bool isVerified = await _userRepository.linkWithOTP(
|
final bool isVerified = await _userRepository.linkWithOTP(
|
||||||
event.phoneNumber, _verificationId!, event.code);
|
event.phoneNumber, _verificationId ?? '', event.code);
|
||||||
|
|
||||||
if (isVerified) {
|
if (isVerified) {
|
||||||
emit(AuthStateSuccess());
|
emit(AuthStateSuccess());
|
||||||
@@ -201,34 +152,7 @@ class AuthBloc extends Bloc<AuthEvent, AuthState> {
|
|||||||
emit(const AuthStateVerifyOAuth(true));
|
emit(const AuthStateVerifyOAuth(true));
|
||||||
}
|
}
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
if (e is FirebaseAuthException) {
|
emit(AuthStateFailure(message: "Error inesperado. $e"));
|
||||||
switch (e.code) {
|
|
||||||
case "invalid-verification-code":
|
|
||||||
emit(const AuthStateFailure(
|
|
||||||
message: "Código de verificación incorrecto. 🥸",
|
|
||||||
));
|
|
||||||
break;
|
|
||||||
|
|
||||||
case "provider-already-linked":
|
|
||||||
emit(const AuthStateFailure(
|
|
||||||
message: "Cuenta ya vinculada. 🥸",
|
|
||||||
));
|
|
||||||
break;
|
|
||||||
|
|
||||||
case "credential-already-in-use":
|
|
||||||
emit(const AuthStateFailure(
|
|
||||||
message:
|
|
||||||
"Este numero ya se encuentra registrado con otra cuenta. 🥸",
|
|
||||||
));
|
|
||||||
break;
|
|
||||||
|
|
||||||
default:
|
|
||||||
emit(AuthStateFailure(message: "Error inesperado. 🥸 $e"));
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
emit(AuthStateFailure(message: "Error inesperado. 🥸 $e"));
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -8,10 +8,10 @@ part 'chat_event.dart';
|
|||||||
part 'chat_state.dart';
|
part 'chat_state.dart';
|
||||||
|
|
||||||
class ChatBloc extends Bloc<ChatEvent, ChatState> {
|
class ChatBloc extends Bloc<ChatEvent, ChatState> {
|
||||||
final FirebaseChatRepository _chatRepository;
|
final ApiChatRepository _chatRepository;
|
||||||
|
|
||||||
ChatBloc({
|
ChatBloc({
|
||||||
required FirebaseChatRepository chatRepository,
|
required ApiChatRepository chatRepository,
|
||||||
}) : _chatRepository = chatRepository,
|
}) : _chatRepository = chatRepository,
|
||||||
super(ChatInitial()) {
|
super(ChatInitial()) {
|
||||||
on<LoadChatEvent>(_onLoadChatEvent);
|
on<LoadChatEvent>(_onLoadChatEvent);
|
||||||
|
|||||||
@@ -1,25 +1,16 @@
|
|||||||
import 'dart:developer';
|
import 'dart:developer';
|
||||||
|
|
||||||
import 'package:cloud_firestore/cloud_firestore.dart';
|
|
||||||
import 'package:firebase_auth/firebase_auth.dart';
|
|
||||||
import 'package:firebase_core/firebase_core.dart';
|
|
||||||
import 'package:firebase_messaging/firebase_messaging.dart';
|
|
||||||
import 'package:flutter_bloc/flutter_bloc.dart';
|
import 'package:flutter_bloc/flutter_bloc.dart';
|
||||||
import 'package:equatable/equatable.dart';
|
import 'package:equatable/equatable.dart';
|
||||||
import 'package:prosappco/firebase_options.dart';
|
|
||||||
import 'package:prosappco/local_notifications/local_notifications.dart';
|
|
||||||
import 'package:user_repository/user_repository.dart';
|
import 'package:user_repository/user_repository.dart';
|
||||||
|
|
||||||
part 'notification_event.dart';
|
part 'notification_event.dart';
|
||||||
part 'notification_state.dart';
|
part 'notification_state.dart';
|
||||||
|
|
||||||
Future<void> firebaseMessagingBackgroundHandler(RemoteMessage message) async {
|
// Stub: Firebase messaging removed. Background handler is no longer needed.
|
||||||
await Firebase.initializeApp();
|
Future<void> firebaseMessagingBackgroundHandler(dynamic message) async {}
|
||||||
}
|
|
||||||
|
|
||||||
class NotificationBloc extends Bloc<NotificationEvent, NotificationState> {
|
class NotificationBloc extends Bloc<NotificationEvent, NotificationState> {
|
||||||
FirebaseMessaging messaging = FirebaseMessaging.instance;
|
|
||||||
|
|
||||||
final Future<void> Function() requestLocalNotificationPermission;
|
final Future<void> Function() requestLocalNotificationPermission;
|
||||||
final void Function({
|
final void Function({
|
||||||
required int id,
|
required int id,
|
||||||
@@ -33,83 +24,18 @@ class NotificationBloc extends Bloc<NotificationEvent, NotificationState> {
|
|||||||
required this.showLocalNotification})
|
required this.showLocalNotification})
|
||||||
: super(const NotificationState()) {
|
: super(const NotificationState()) {
|
||||||
on<NotificationStatusChanged>(_notificationStatusChanged);
|
on<NotificationStatusChanged>(_notificationStatusChanged);
|
||||||
|
|
||||||
_initialStatusCheck();
|
|
||||||
|
|
||||||
_onForegroundMessage();
|
|
||||||
}
|
|
||||||
|
|
||||||
static Future<void> initializeFCM() async {
|
|
||||||
await Firebase.initializeApp(
|
|
||||||
options: DefaultFirebaseOptions.currentPlatform,
|
|
||||||
);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
void _notificationStatusChanged(
|
void _notificationStatusChanged(
|
||||||
NotificationStatusChanged event, Emitter<NotificationState> emit) {
|
NotificationStatusChanged event, Emitter<NotificationState> emit) {
|
||||||
emit(state.copyWith(status: event.status));
|
emit(state.copyWith(status: event.status));
|
||||||
|
|
||||||
_getFCMToken();
|
|
||||||
}
|
|
||||||
|
|
||||||
void _initialStatusCheck() async {
|
|
||||||
final settings = await messaging.getNotificationSettings();
|
|
||||||
add(NotificationStatusChanged(settings.authorizationStatus));
|
|
||||||
_getFCMToken();
|
|
||||||
}
|
|
||||||
|
|
||||||
Future<void> _saveFCMTokenToFirestore(String token) async {
|
|
||||||
try {
|
|
||||||
CollectionReference users = FirebaseFirestore.instance.collection('users');
|
|
||||||
|
|
||||||
await users.doc(FirebaseAuth.instance.currentUser!.uid).update({
|
|
||||||
'token': token,
|
|
||||||
});
|
|
||||||
} catch (e) {
|
|
||||||
print('tokenFCM Error saving FCM token to Firestore: $e');
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
void _getFCMToken() async {
|
|
||||||
if (state.status != AuthorizationStatus.authorized) return;
|
|
||||||
|
|
||||||
final fcmToken = await messaging.getToken();
|
|
||||||
log('tokenFCM ${fcmToken.toString()}');
|
|
||||||
|
|
||||||
if (fcmToken == null) return;
|
|
||||||
|
|
||||||
_saveFCMTokenToFirestore(fcmToken);
|
|
||||||
}
|
|
||||||
|
|
||||||
void _handleRemoteMessage(RemoteMessage message) {
|
|
||||||
if (message.notification == null) return;
|
|
||||||
|
|
||||||
showLocalNotification(
|
|
||||||
id: 1,
|
|
||||||
title: message.notification!.title,
|
|
||||||
body: message.notification!.body,
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
void _onForegroundMessage() {
|
|
||||||
FirebaseMessaging.onMessage.listen(_handleRemoteMessage);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
void requestPermission() async {
|
void requestPermission() async {
|
||||||
NotificationSettings settings = await messaging.requestPermission(
|
try {
|
||||||
alert: true,
|
|
||||||
announcement: false,
|
|
||||||
badge: true,
|
|
||||||
carPlay: false,
|
|
||||||
criticalAlert: false,
|
|
||||||
provisional: false,
|
|
||||||
sound: true,
|
|
||||||
);
|
|
||||||
|
|
||||||
// Solicitar permiso a las local notificaciones
|
|
||||||
|
|
||||||
await requestLocalNotificationPermission();
|
await requestLocalNotificationPermission();
|
||||||
|
} catch (e) {
|
||||||
add(NotificationStatusChanged(settings.authorizationStatus));
|
log('NotificationBloc.requestPermission error: $e');
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,5 +1,13 @@
|
|||||||
part of 'notification_bloc.dart';
|
part of 'notification_bloc.dart';
|
||||||
|
|
||||||
|
// Stub for firebase_messaging AuthorizationStatus
|
||||||
|
enum AuthorizationStatus {
|
||||||
|
authorized,
|
||||||
|
denied,
|
||||||
|
notDetermined,
|
||||||
|
provisional,
|
||||||
|
}
|
||||||
|
|
||||||
class NotificationState extends Equatable {
|
class NotificationState extends Equatable {
|
||||||
final AuthorizationStatus status;
|
final AuthorizationStatus status;
|
||||||
|
|
||||||
|
|||||||
@@ -10,11 +10,11 @@ part 'professional_event.dart';
|
|||||||
part 'professional_state.dart';
|
part 'professional_state.dart';
|
||||||
|
|
||||||
class ProfessionalBloc extends Bloc<ProfessionalEvent, ProfessionalState> {
|
class ProfessionalBloc extends Bloc<ProfessionalEvent, ProfessionalState> {
|
||||||
final FirebaseProfessionalRepository _professionalRepository;
|
final ApiProfessionalRepository _professionalRepository;
|
||||||
final UserRepository _userRepository;
|
final UserRepository _userRepository;
|
||||||
|
|
||||||
ProfessionalBloc(
|
ProfessionalBloc(
|
||||||
{required FirebaseProfessionalRepository professionalRepository,
|
{required ApiProfessionalRepository professionalRepository,
|
||||||
required UserRepository userRepository})
|
required UserRepository userRepository})
|
||||||
: _professionalRepository = professionalRepository,
|
: _professionalRepository = professionalRepository,
|
||||||
_userRepository = userRepository,
|
_userRepository = userRepository,
|
||||||
|
|||||||
@@ -9,11 +9,11 @@ part 'professional_list_state.dart';
|
|||||||
class ProfessionalListBloc
|
class ProfessionalListBloc
|
||||||
extends Bloc<ProfessionalListEvent, ProfessionalListState> {
|
extends Bloc<ProfessionalListEvent, ProfessionalListState> {
|
||||||
final UserRepository _userRepository;
|
final UserRepository _userRepository;
|
||||||
final FirebaseProfessionalRepository _firebaseProfessionalRepository;
|
final ApiProfessionalRepository _firebaseProfessionalRepository;
|
||||||
|
|
||||||
ProfessionalListBloc({
|
ProfessionalListBloc({
|
||||||
required UserRepository userRepository,
|
required UserRepository userRepository,
|
||||||
required FirebaseProfessionalRepository firebaseProfessonalRepository,
|
required ApiProfessionalRepository firebaseProfessonalRepository,
|
||||||
}) : _userRepository = userRepository,
|
}) : _userRepository = userRepository,
|
||||||
_firebaseProfessionalRepository = firebaseProfessonalRepository,
|
_firebaseProfessionalRepository = firebaseProfessonalRepository,
|
||||||
super(ProfessionalListInitial()) {
|
super(ProfessionalListInitial()) {
|
||||||
|
|||||||
@@ -7,10 +7,10 @@ part 'professional_profile_state.dart';
|
|||||||
|
|
||||||
class ProfessionalProfileBloc
|
class ProfessionalProfileBloc
|
||||||
extends Bloc<ProfessionalProfileEvent, ProfessionalProfileState> {
|
extends Bloc<ProfessionalProfileEvent, ProfessionalProfileState> {
|
||||||
final FirebaseProfessionalRepository _professionalRepository;
|
final ApiProfessionalRepository _professionalRepository;
|
||||||
|
|
||||||
ProfessionalProfileBloc(
|
ProfessionalProfileBloc(
|
||||||
{required FirebaseProfessionalRepository professionalRepository})
|
{required ApiProfessionalRepository professionalRepository})
|
||||||
: _professionalRepository = professionalRepository,
|
: _professionalRepository = professionalRepository,
|
||||||
super((UpdateProfessionalInfoInitial())) {
|
super((UpdateProfessionalInfoInitial())) {
|
||||||
on<UpdateProfessionalBannerInfo>(_onUpdateProfessionalBannerInfo);
|
on<UpdateProfessionalBannerInfo>(_onUpdateProfessionalBannerInfo);
|
||||||
|
|||||||
@@ -9,11 +9,11 @@ part 'score_event.dart';
|
|||||||
part 'score_state.dart';
|
part 'score_state.dart';
|
||||||
|
|
||||||
class ScoreBloc extends Bloc<ScoreEvent, ScoreState> {
|
class ScoreBloc extends Bloc<ScoreEvent, ScoreState> {
|
||||||
final FirebaseScoreRepository _scoreRepository;
|
final ApiScoreRepository _scoreRepository;
|
||||||
final UserRepository _userRepository;
|
final UserRepository _userRepository;
|
||||||
|
|
||||||
ScoreBloc({
|
ScoreBloc({
|
||||||
required FirebaseScoreRepository scoreRepository,
|
required ApiScoreRepository scoreRepository,
|
||||||
required UserRepository userRepository,
|
required UserRepository userRepository,
|
||||||
}) : _scoreRepository = scoreRepository,
|
}) : _scoreRepository = scoreRepository,
|
||||||
_userRepository = userRepository,
|
_userRepository = userRepository,
|
||||||
|
|||||||
@@ -1,6 +1,5 @@
|
|||||||
import 'dart:developer';
|
import 'dart:developer';
|
||||||
|
|
||||||
import 'package:cloud_firestore/cloud_firestore.dart';
|
|
||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
import 'package:flutter_bloc/flutter_bloc.dart';
|
import 'package:flutter_bloc/flutter_bloc.dart';
|
||||||
import 'package:equatable/equatable.dart';
|
import 'package:equatable/equatable.dart';
|
||||||
@@ -13,14 +12,14 @@ part 'service_event.dart';
|
|||||||
part 'service_state.dart';
|
part 'service_state.dart';
|
||||||
|
|
||||||
class ServiceBloc extends Bloc<ServiceEvent, ServiceState> {
|
class ServiceBloc extends Bloc<ServiceEvent, ServiceState> {
|
||||||
final FirebaseServiceRepository _serviceRepository;
|
final ApiServiceRepository _serviceRepository;
|
||||||
final UserRepository _userRepository;
|
final UserRepository _userRepository;
|
||||||
final FirebaseProfessionalRepository _professionalRepository;
|
final ApiProfessionalRepository _professionalRepository;
|
||||||
|
|
||||||
ServiceBloc({
|
ServiceBloc({
|
||||||
required FirebaseServiceRepository serviceRepository,
|
required ApiServiceRepository serviceRepository,
|
||||||
required UserRepository userRepository,
|
required UserRepository userRepository,
|
||||||
required FirebaseProfessionalRepository professionRepository,
|
required ApiProfessionalRepository professionRepository,
|
||||||
}) : _serviceRepository = serviceRepository,
|
}) : _serviceRepository = serviceRepository,
|
||||||
_userRepository = userRepository,
|
_userRepository = userRepository,
|
||||||
_professionalRepository = professionRepository,
|
_professionalRepository = professionRepository,
|
||||||
|
|||||||
@@ -117,7 +117,7 @@ class CreateService extends ServiceEvent {
|
|||||||
final double latitude;
|
final double latitude;
|
||||||
final double longitude;
|
final double longitude;
|
||||||
final String day;
|
final String day;
|
||||||
final Timestamp createdAt;
|
final String createdAt;
|
||||||
final String description;
|
final String description;
|
||||||
final TimeOfDay range1Hour1;
|
final TimeOfDay range1Hour1;
|
||||||
final TimeOfDay range1Hour2;
|
final TimeOfDay range1Hour2;
|
||||||
|
|||||||
@@ -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';
|
||||||
@@ -233,9 +232,9 @@ class GeneralDrawer extends StatelessWidget {
|
|||||||
);
|
);
|
||||||
},
|
},
|
||||||
future: Injector.appInstance
|
future: Injector.appInstance
|
||||||
.get<FirebaseServiceRepository>()
|
.get<ApiServiceRepository>()
|
||||||
.countPendingServicesForProfessional(
|
.countPendingServicesForProfessional(
|
||||||
FirebaseAuth.instance.currentUser!.uid),
|
ApiUserRepository.currentUserId ?? ''),
|
||||||
)
|
)
|
||||||
: const Icon(
|
: const Icon(
|
||||||
Icons.keyboard_arrow_right,
|
Icons.keyboard_arrow_right,
|
||||||
@@ -477,9 +476,9 @@ class GeneralDrawer extends StatelessWidget {
|
|||||||
);
|
);
|
||||||
},
|
},
|
||||||
future: Injector.appInstance
|
future: Injector.appInstance
|
||||||
.get<FirebaseServiceRepository>()
|
.get<ApiServiceRepository>()
|
||||||
.countPendingServicesForProfessional(
|
.countPendingServicesForProfessional(
|
||||||
FirebaseAuth.instance.currentUser!.uid),
|
ApiUserRepository.currentUserId ?? ''),
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
)
|
)
|
||||||
|
|||||||
@@ -23,7 +23,7 @@ class _GeneralReputationState extends State<GeneralReputation> {
|
|||||||
void initState() {
|
void initState() {
|
||||||
super.initState();
|
super.initState();
|
||||||
|
|
||||||
final repository = Injector.appInstance.get<FirebaseScoreRepository>();
|
final repository = Injector.appInstance.get<ApiScoreRepository>();
|
||||||
reputation = repository.getReputationByUserId(widget.userId);
|
reputation = repository.getReputationByUserId(widget.userId);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -1,5 +1,7 @@
|
|||||||
const String GOOGLE_MAPS_API_KEY = 'AIzaSyCW_og6qQ8W8G-5_BxIS4sBnl8cLkjL95s';
|
const String GOOGLE_MAPS_API_KEY = 'AIzaSyCW_og6qQ8W8G-5_BxIS4sBnl8cLkjL95s';
|
||||||
|
|
||||||
|
const String API_BASE_URL = String.fromEnvironment('API_BASE_URL', defaultValue: 'http://localhost:3000/api/v1');
|
||||||
|
|
||||||
const String MAP_STYLE = '''
|
const String MAP_STYLE = '''
|
||||||
[
|
[
|
||||||
{
|
{
|
||||||
|
|||||||
+15
-20
@@ -1,5 +1,4 @@
|
|||||||
import 'package:chat_repository/chat_repository.dart';
|
import 'package:chat_repository/chat_repository.dart';
|
||||||
import 'package:firebase_auth/firebase_auth.dart';
|
|
||||||
import 'package:injector/injector.dart';
|
import 'package:injector/injector.dart';
|
||||||
import 'package:profession_repository/profession_repository.dart';
|
import 'package:profession_repository/profession_repository.dart';
|
||||||
import 'package:professional_repository/professional_repository.dart';
|
import 'package:professional_repository/professional_repository.dart';
|
||||||
@@ -29,20 +28,20 @@ class AppDI {
|
|||||||
final injector = Injector.appInstance;
|
final injector = Injector.appInstance;
|
||||||
|
|
||||||
injector.registerSingleton<UserRepository>(
|
injector.registerSingleton<UserRepository>(
|
||||||
(() => FirebaseUserRepository(FirebaseAuth.instance)));
|
(() => ApiUserRepository()));
|
||||||
|
|
||||||
injector.registerSingleton<CityRepository>(() => FirebaseCityRepository());
|
injector.registerSingleton<CityRepository>(() => ApiCityRepository());
|
||||||
|
|
||||||
injector.registerSingleton<ProfessionRepository>(
|
injector.registerSingleton<ProfessionRepository>(
|
||||||
() => FirebaseProfessionRepository());
|
() => ApiProfessionRepository());
|
||||||
|
|
||||||
injector.registerSingleton<SettingRepository>(
|
injector.registerSingleton<SettingRepository>(
|
||||||
() => FirebaseSettingRepository());
|
() => ApiSettingRepository());
|
||||||
|
|
||||||
injector.registerSingleton(() => FirebaseProfessionalRepository());
|
injector.registerSingleton(() => ApiProfessionalRepository());
|
||||||
injector.registerSingleton(() => FirebaseServiceRepository());
|
injector.registerSingleton(() => ApiServiceRepository());
|
||||||
injector.registerSingleton(() => FirebaseChatRepository());
|
injector.registerSingleton(() => ApiChatRepository());
|
||||||
injector.registerSingleton(() => FirebaseScoreRepository());
|
injector.registerSingleton(() => ApiScoreRepository());
|
||||||
|
|
||||||
injector.registerSingleton<AuthenticationBloc>((() =>
|
injector.registerSingleton<AuthenticationBloc>((() =>
|
||||||
AuthenticationBloc(myUserRepository: injector.get<UserRepository>())));
|
AuthenticationBloc(myUserRepository: injector.get<UserRepository>())));
|
||||||
@@ -60,7 +59,8 @@ class AppDI {
|
|||||||
);
|
);
|
||||||
injector.registerSingleton(
|
injector.registerSingleton(
|
||||||
() => NotificationBloc(
|
() => NotificationBloc(
|
||||||
requestLocalNotificationPermission: LocalNotifications.requestPermissionLocalNotifications,
|
requestLocalNotificationPermission:
|
||||||
|
LocalNotifications.requestPermissionLocalNotifications,
|
||||||
showLocalNotification: LocalNotifications.showLocalNotification,
|
showLocalNotification: LocalNotifications.showLocalNotification,
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
@@ -84,31 +84,26 @@ class AppDI {
|
|||||||
() => ProfessionalListBloc(
|
() => ProfessionalListBloc(
|
||||||
userRepository: injector.get<UserRepository>(),
|
userRepository: injector.get<UserRepository>(),
|
||||||
firebaseProfessonalRepository:
|
firebaseProfessonalRepository:
|
||||||
injector.get<FirebaseProfessionalRepository>()),
|
injector.get<ApiProfessionalRepository>()),
|
||||||
);
|
);
|
||||||
|
|
||||||
injector.registerDependency<ChatBloc>(() => ChatBloc(
|
injector.registerDependency<ChatBloc>(() => ChatBloc(
|
||||||
chatRepository: injector.get<FirebaseChatRepository>(),
|
chatRepository: injector.get<ApiChatRepository>(),
|
||||||
));
|
));
|
||||||
|
|
||||||
injector.registerDependency<ServiceBloc>(
|
injector.registerDependency<ServiceBloc>(
|
||||||
() => ServiceBloc(
|
() => ServiceBloc(
|
||||||
serviceRepository: injector.get<FirebaseServiceRepository>(),
|
serviceRepository: injector.get<ApiServiceRepository>(),
|
||||||
userRepository: injector.get<UserRepository>(),
|
userRepository: injector.get<UserRepository>(),
|
||||||
professionRepository: injector.get<FirebaseProfessionalRepository>(),
|
professionRepository: injector.get<ApiProfessionalRepository>(),
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
|
|
||||||
injector.registerDependency<ScoreBloc>(
|
injector.registerDependency<ScoreBloc>(
|
||||||
() => ScoreBloc(
|
() => ScoreBloc(
|
||||||
scoreRepository: injector.get<FirebaseScoreRepository>(),
|
scoreRepository: injector.get<ApiScoreRepository>(),
|
||||||
userRepository: injector.get<UserRepository>(),
|
userRepository: injector.get<UserRepository>(),
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
|
|
||||||
// injector.registerSingleton((() => ScoreBloc(
|
|
||||||
// firebaseScoreRepository: injector.get(),
|
|
||||||
// userRepository: injector.get<UserRepository>(),
|
|
||||||
// )));
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,6 +1,7 @@
|
|||||||
import 'package:flutter_local_notifications/flutter_local_notifications.dart';
|
import 'package:flutter_local_notifications/flutter_local_notifications.dart';
|
||||||
import 'package:http/http.dart' as http;
|
import 'package:http/http.dart' as http;
|
||||||
import 'dart:convert';
|
import 'dart:convert';
|
||||||
|
import 'package:prosappco/constansts.dart' show API_BASE_URL;
|
||||||
|
|
||||||
class LocalNotifications {
|
class LocalNotifications {
|
||||||
static Future<void> requestPermissionLocalNotifications() async {
|
static Future<void> requestPermissionLocalNotifications() async {
|
||||||
@@ -76,25 +77,14 @@ class LocalNotifications {
|
|||||||
String token, String title, String body) async {
|
String token, String title, String body) async {
|
||||||
try {
|
try {
|
||||||
http.Response response = await http.post(
|
http.Response response = await http.post(
|
||||||
Uri.parse('https://fcm.googleapis.com/fcm/send'),
|
Uri.parse('$API_BASE_URL/notifications/send'),
|
||||||
headers: <String, String>{
|
headers: <String, String>{
|
||||||
'Content-Type': 'application/json; charset=UTF-8',
|
'Content-Type': 'application/json; charset=UTF-8',
|
||||||
'Authorization':
|
|
||||||
'key=AAAAORdR-xU:APA91bF_wblg86jHAC-uexrXPHavYRlk5wge1Gf46m56V4J2D2L37Cp_hf46JZUzpvsWPSpqc5ewHelKI9LTifUG_s2mciMI6e5VLKo7E1R8btbNo7iaM9do2ctoyHKUm1atlZBdKaN2',
|
|
||||||
},
|
},
|
||||||
body: jsonEncode(
|
body: jsonEncode(
|
||||||
<String, dynamic>{
|
<String, dynamic>{
|
||||||
'notification': <String, dynamic>{
|
|
||||||
'body': body,
|
|
||||||
'title': title,
|
'title': title,
|
||||||
|
'body': body,
|
||||||
},
|
|
||||||
'priority': 'high',
|
|
||||||
'data': <String, dynamic>{
|
|
||||||
'click_action': 'FLUTTER_NOTIFICATION_CLICK',
|
|
||||||
'id': '1',
|
|
||||||
'status': 'done',
|
|
||||||
},
|
|
||||||
'to': token,
|
'to': token,
|
||||||
},
|
},
|
||||||
),
|
),
|
||||||
|
|||||||
+1
-9
@@ -1,6 +1,4 @@
|
|||||||
import 'package:firebase_messaging/firebase_messaging.dart';
|
|
||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
import 'package:firebase_core/firebase_core.dart';
|
|
||||||
import 'package:flutter/services.dart';
|
import 'package:flutter/services.dart';
|
||||||
import 'package:flutter_bloc/flutter_bloc.dart';
|
import 'package:flutter_bloc/flutter_bloc.dart';
|
||||||
import 'package:injector/injector.dart';
|
import 'package:injector/injector.dart';
|
||||||
@@ -14,19 +12,13 @@ import 'package:prosappco/blocs/profile_bloc/profile_bloc.dart';
|
|||||||
import 'package:prosappco/dependency/app_di.dart';
|
import 'package:prosappco/dependency/app_di.dart';
|
||||||
import 'package:prosappco/local_notifications/local_notifications.dart';
|
import 'package:prosappco/local_notifications/local_notifications.dart';
|
||||||
import 'simple_bloc_observer.dart';
|
import 'simple_bloc_observer.dart';
|
||||||
import 'firebase_options.dart';
|
|
||||||
|
|
||||||
void main() async {
|
void main() async {
|
||||||
WidgetsFlutterBinding.ensureInitialized();
|
WidgetsFlutterBinding.ensureInitialized();
|
||||||
await Firebase.initializeApp(
|
|
||||||
options: DefaultFirebaseOptions.currentPlatform,
|
|
||||||
); // Asegúrate de inicializar Firebase antes de cualquier otra operación relacionada con Firebase.
|
|
||||||
|
|
||||||
// Inicialización de notificaciones locales después de Firebase.
|
// Inicializacion de notificaciones locales
|
||||||
await LocalNotifications.initializeLocalNotifications();
|
await LocalNotifications.initializeLocalNotifications();
|
||||||
|
|
||||||
FirebaseMessaging.onBackgroundMessage(firebaseMessagingBackgroundHandler);
|
|
||||||
|
|
||||||
Bloc.observer = SimpleBlocObserver();
|
Bloc.observer = SimpleBlocObserver();
|
||||||
SystemChrome.setPreferredOrientations([DeviceOrientation.portraitUp]);
|
SystemChrome.setPreferredOrientations([DeviceOrientation.portraitUp]);
|
||||||
AppDI().register();
|
AppDI().register();
|
||||||
|
|||||||
@@ -1,5 +1,4 @@
|
|||||||
import 'package:chat_repository/chat_repository.dart';
|
import 'package:chat_repository/chat_repository.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:flutter_bloc/flutter_bloc.dart';
|
import 'package:flutter_bloc/flutter_bloc.dart';
|
||||||
@@ -111,7 +110,7 @@ class _ChatScreenState extends State<ChatScreen> {
|
|||||||
),
|
),
|
||||||
),
|
),
|
||||||
subtitle: widget.service.userId ==
|
subtitle: widget.service.userId ==
|
||||||
FirebaseAuth.instance.currentUser!.uid
|
ApiUserRepository.currentUserId ?? ''
|
||||||
? GeneralReputation(
|
? GeneralReputation(
|
||||||
userId: widget.service.professionalId,
|
userId: widget.service.professionalId,
|
||||||
builder: (context, reputation) {
|
builder: (context, reputation) {
|
||||||
@@ -229,7 +228,7 @@ class _ChatScreenState extends State<ChatScreen> {
|
|||||||
|
|
||||||
MessageEntity message = MessageEntity(
|
MessageEntity message = MessageEntity(
|
||||||
ownerId:
|
ownerId:
|
||||||
FirebaseAuth.instance.currentUser!.uid,
|
ApiUserRepository.currentUserId ?? '',
|
||||||
content: _messageController.text.trim(),
|
content: _messageController.text.trim(),
|
||||||
createdAt: DateTime.now(),
|
createdAt: DateTime.now(),
|
||||||
);
|
);
|
||||||
@@ -272,7 +271,7 @@ class _ChatScreenState extends State<ChatScreen> {
|
|||||||
}
|
}
|
||||||
|
|
||||||
MessageEntity message = MessageEntity(
|
MessageEntity message = MessageEntity(
|
||||||
ownerId: FirebaseAuth.instance.currentUser!.uid,
|
ownerId: ApiUserRepository.currentUserId ?? '',
|
||||||
content: _messageController.text.trim(),
|
content: _messageController.text.trim(),
|
||||||
createdAt: DateTime.now(),
|
createdAt: DateTime.now(),
|
||||||
);
|
);
|
||||||
@@ -340,7 +339,7 @@ class _ChatScreenState extends State<ChatScreen> {
|
|||||||
List<Widget> _messagesList(List<MessageEntity> messages) {
|
List<Widget> _messagesList(List<MessageEntity> messages) {
|
||||||
return messages
|
return messages
|
||||||
.map(
|
.map(
|
||||||
(e) => e.ownerId != FirebaseAuth.instance.currentUser!.uid
|
(e) => e.ownerId != ApiUserRepository.currentUserId ?? ''
|
||||||
? ListTile(
|
? ListTile(
|
||||||
title: Column(
|
title: Column(
|
||||||
mainAxisAlignment: MainAxisAlignment.start,
|
mainAxisAlignment: MainAxisAlignment.start,
|
||||||
@@ -437,12 +436,12 @@ class _ChatScreenState extends State<ChatScreen> {
|
|||||||
|
|
||||||
Future<List<dynamic>> _getUserAndProfessionalInfo(
|
Future<List<dynamic>> _getUserAndProfessionalInfo(
|
||||||
ServiceEntity service) async {
|
ServiceEntity service) async {
|
||||||
final userRepo = FirebaseUserRepository(FirebaseAuth.instance);
|
final userRepo = Injector.appInstance.get<UserRepository>();
|
||||||
|
|
||||||
final MyUser? userInfo;
|
final MyUser? userInfo;
|
||||||
final MyUser? myInfo;
|
final MyUser? myInfo;
|
||||||
|
|
||||||
if (FirebaseAuth.instance.currentUser!.uid == service.userId) {
|
if ((ApiUserRepository.currentUserId ?? '') == service.userId) {
|
||||||
userInfo = await userRepo.getMyUser(service.professionalId);
|
userInfo = await userRepo.getMyUser(service.professionalId);
|
||||||
myInfo = await userRepo.getMyUser(service.userId);
|
myInfo = await userRepo.getMyUser(service.userId);
|
||||||
} else {
|
} else {
|
||||||
|
|||||||
@@ -1,4 +1,5 @@
|
|||||||
import 'package:firebase_auth/firebase_auth.dart';
|
import 'package:user_repository/user_repository.dart';
|
||||||
|
|
||||||
import 'package:flutter/cupertino.dart';
|
import 'package:flutter/cupertino.dart';
|
||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
import 'package:flutter_bloc/flutter_bloc.dart';
|
import 'package:flutter_bloc/flutter_bloc.dart';
|
||||||
@@ -173,13 +174,13 @@ class _ProfessionalListScreenState extends State<ProfessionalListScreen> {
|
|||||||
.toLowerCase()
|
.toLowerCase()
|
||||||
.contains(removeDiacritics(_searchController.text.toLowerCase())))
|
.contains(removeDiacritics(_searchController.text.toLowerCase())))
|
||||||
.where((user) =>
|
.where((user) =>
|
||||||
user.myUser.id != FirebaseAuth.instance.currentUser!.uid)
|
user.myUser.id != ApiUserRepository.currentUserId ?? '')
|
||||||
.where(
|
.where(
|
||||||
(user) => user.professionalInfo.profession == _selectedProfession)
|
(user) => user.professionalInfo.profession == _selectedProfession)
|
||||||
.toList();
|
.toList();
|
||||||
// } else {
|
// } else {
|
||||||
// filteredUsers = state.users
|
// filteredUsers = state.users
|
||||||
// // .where((user) => user.myUser.id != FirebaseAuth.instance.currentUser!.uid)
|
// // .where((user) => user.myUser.id != ApiUserRepository.currentUserId ?? '')
|
||||||
// .toList();
|
// .toList();
|
||||||
// }
|
// }
|
||||||
|
|
||||||
|
|||||||
@@ -1,4 +1,5 @@
|
|||||||
import 'package:firebase_auth/firebase_auth.dart';
|
import 'package:user_repository/user_repository.dart';
|
||||||
|
|
||||||
import 'package:flutter/cupertino.dart';
|
import 'package:flutter/cupertino.dart';
|
||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
import 'package:flutter_bloc/flutter_bloc.dart';
|
import 'package:flutter_bloc/flutter_bloc.dart';
|
||||||
@@ -28,7 +29,7 @@ class _ProfessionalPendingServiceListScreenState
|
|||||||
|
|
||||||
serviceBloc = Injector.appInstance.get<ServiceBloc>();
|
serviceBloc = Injector.appInstance.get<ServiceBloc>();
|
||||||
|
|
||||||
serviceBloc.add(LoadPendingServicesForProfessional(FirebaseAuth.instance.currentUser!.uid));
|
serviceBloc.add(LoadPendingServicesForProfessional(ApiUserRepository.currentUserId ?? ''));
|
||||||
}
|
}
|
||||||
|
|
||||||
@override
|
@override
|
||||||
|
|||||||
@@ -1,4 +1,5 @@
|
|||||||
import 'package:firebase_auth/firebase_auth.dart';
|
import 'package:user_repository/user_repository.dart';
|
||||||
|
|
||||||
import 'package:flutter/cupertino.dart';
|
import 'package:flutter/cupertino.dart';
|
||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
import 'package:flutter_bloc/flutter_bloc.dart';
|
import 'package:flutter_bloc/flutter_bloc.dart';
|
||||||
@@ -25,7 +26,7 @@ class _ProfessionalScoreListScreenState
|
|||||||
scoreBloc = Injector.appInstance.get<ScoreBloc>();
|
scoreBloc = Injector.appInstance.get<ScoreBloc>();
|
||||||
|
|
||||||
scoreBloc.add(LoadScoresForProfessionalEvent(
|
scoreBloc.add(LoadScoresForProfessionalEvent(
|
||||||
userId: FirebaseAuth.instance.currentUser!.uid));
|
userId: ApiUserRepository.currentUserId ?? ''));
|
||||||
}
|
}
|
||||||
|
|
||||||
@override
|
@override
|
||||||
|
|||||||
@@ -1,4 +1,5 @@
|
|||||||
import 'package:firebase_auth/firebase_auth.dart';
|
import 'package:user_repository/user_repository.dart';
|
||||||
|
|
||||||
import 'package:flutter/cupertino.dart';
|
import 'package:flutter/cupertino.dart';
|
||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
import 'package:flutter_bloc/flutter_bloc.dart';
|
import 'package:flutter_bloc/flutter_bloc.dart';
|
||||||
@@ -29,7 +30,7 @@ class _ProfessionalServiceHistoryListScreenState
|
|||||||
serviceBloc = Injector.appInstance.get<ServiceBloc>();
|
serviceBloc = Injector.appInstance.get<ServiceBloc>();
|
||||||
|
|
||||||
serviceBloc.add(LoadServicesHistoryForProfessional(
|
serviceBloc.add(LoadServicesHistoryForProfessional(
|
||||||
FirebaseAuth.instance.currentUser!.uid));
|
ApiUserRepository.currentUserId ?? ''));
|
||||||
}
|
}
|
||||||
|
|
||||||
@override
|
@override
|
||||||
|
|||||||
@@ -1,4 +1,5 @@
|
|||||||
import 'package:firebase_auth/firebase_auth.dart';
|
import 'package:user_repository/user_repository.dart';
|
||||||
|
|
||||||
import 'package:flutter/cupertino.dart';
|
import 'package:flutter/cupertino.dart';
|
||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
import 'package:flutter_bloc/flutter_bloc.dart';
|
import 'package:flutter_bloc/flutter_bloc.dart';
|
||||||
@@ -28,7 +29,7 @@ class _ProfessionalServiceListScreenState
|
|||||||
|
|
||||||
serviceBloc = Injector.appInstance.get<ServiceBloc>();
|
serviceBloc = Injector.appInstance.get<ServiceBloc>();
|
||||||
|
|
||||||
serviceBloc.add(LoadServicesForProfessional(FirebaseAuth.instance.currentUser!.uid));
|
serviceBloc.add(LoadServicesForProfessional(ApiUserRepository.currentUserId ?? ''));
|
||||||
}
|
}
|
||||||
|
|
||||||
@override
|
@override
|
||||||
|
|||||||
@@ -1,4 +1,5 @@
|
|||||||
import 'package:firebase_auth/firebase_auth.dart';
|
import 'package:user_repository/user_repository.dart';
|
||||||
|
|
||||||
import 'package:flutter/cupertino.dart';
|
import 'package:flutter/cupertino.dart';
|
||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
import 'package:flutter_bloc/flutter_bloc.dart';
|
import 'package:flutter_bloc/flutter_bloc.dart';
|
||||||
@@ -24,7 +25,7 @@ class _UserScoreListScreenState extends State<UserScoreListScreen> {
|
|||||||
scoreBloc = Injector.appInstance.get<ScoreBloc>();
|
scoreBloc = Injector.appInstance.get<ScoreBloc>();
|
||||||
|
|
||||||
scoreBloc.add(
|
scoreBloc.add(
|
||||||
LoadScoresForUserEvent(userId: FirebaseAuth.instance.currentUser!.uid));
|
LoadScoresForUserEvent(userId: ApiUserRepository.currentUserId ?? ''));
|
||||||
}
|
}
|
||||||
|
|
||||||
@override
|
@override
|
||||||
|
|||||||
@@ -1,4 +1,5 @@
|
|||||||
import 'package:firebase_auth/firebase_auth.dart';
|
import 'package:user_repository/user_repository.dart';
|
||||||
|
|
||||||
import 'package:flutter/cupertino.dart';
|
import 'package:flutter/cupertino.dart';
|
||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
import 'package:flutter_bloc/flutter_bloc.dart';
|
import 'package:flutter_bloc/flutter_bloc.dart';
|
||||||
@@ -29,7 +30,7 @@ class _UserServiceHistoryListScreenState
|
|||||||
serviceBloc = Injector.appInstance.get<ServiceBloc>();
|
serviceBloc = Injector.appInstance.get<ServiceBloc>();
|
||||||
|
|
||||||
serviceBloc.add(
|
serviceBloc.add(
|
||||||
LoadServicesHistoryForUser(FirebaseAuth.instance.currentUser!.uid));
|
LoadServicesHistoryForUser(ApiUserRepository.currentUserId ?? ''));
|
||||||
}
|
}
|
||||||
|
|
||||||
@override
|
@override
|
||||||
|
|||||||
@@ -1,4 +1,5 @@
|
|||||||
import 'package:firebase_auth/firebase_auth.dart';
|
import 'package:user_repository/user_repository.dart';
|
||||||
|
|
||||||
import 'package:flutter/cupertino.dart';
|
import 'package:flutter/cupertino.dart';
|
||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
import 'package:flutter_bloc/flutter_bloc.dart';
|
import 'package:flutter_bloc/flutter_bloc.dart';
|
||||||
@@ -27,7 +28,7 @@ class _UserServiceListScreenState extends State<UserServiceListScreen> {
|
|||||||
serviceBloc = Injector.appInstance.get<ServiceBloc>();
|
serviceBloc = Injector.appInstance.get<ServiceBloc>();
|
||||||
|
|
||||||
serviceBloc
|
serviceBloc
|
||||||
.add(LoadServicesForUser(FirebaseAuth.instance.currentUser!.uid));
|
.add(LoadServicesForUser(ApiUserRepository.currentUserId ?? ''));
|
||||||
}
|
}
|
||||||
|
|
||||||
@override
|
@override
|
||||||
|
|||||||
@@ -1,4 +1,3 @@
|
|||||||
import 'package:cloud_firestore/cloud_firestore.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';
|
||||||
@@ -28,7 +27,7 @@ class ProfessionalCalendarScreen extends StatefulWidget {
|
|||||||
class _ProfessionalCalendarScreenState extends State<ProfessionalCalendarScreen> {
|
class _ProfessionalCalendarScreenState extends State<ProfessionalCalendarScreen> {
|
||||||
final settingRepository = Injector.appInstance.get<SettingRepository>();
|
final settingRepository = Injector.appInstance.get<SettingRepository>();
|
||||||
final serviceRepository =
|
final serviceRepository =
|
||||||
Injector.appInstance.get<FirebaseServiceRepository>();
|
Injector.appInstance.get<ApiServiceRepository>();
|
||||||
SettingEntity? settings;
|
SettingEntity? settings;
|
||||||
|
|
||||||
DateTime today = DateTime.now();
|
DateTime today = DateTime.now();
|
||||||
@@ -352,7 +351,7 @@ class _ProfessionalCalendarScreenState extends State<ProfessionalCalendarScreen>
|
|||||||
latitude: 0,
|
latitude: 0,
|
||||||
longitude: 0,
|
longitude: 0,
|
||||||
day: today.toString(),
|
day: today.toString(),
|
||||||
createdAt: Timestamp.now(),
|
createdAt: DateTime.now().toIso8601String(),
|
||||||
description: '',
|
description: '',
|
||||||
range1Hour1: time,
|
range1Hour1: time,
|
||||||
range1Hour2: time.add(hour: 2),
|
range1Hour2: time.add(hour: 2),
|
||||||
|
|||||||
@@ -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:flutter_bloc/flutter_bloc.dart';
|
import 'package:flutter_bloc/flutter_bloc.dart';
|
||||||
@@ -33,7 +31,7 @@ class _ScoreScreenState extends State<ScoreScreen> {
|
|||||||
super.initState();
|
super.initState();
|
||||||
_userInfoFuture = _getUserAndProfessionalInfo(widget.service);
|
_userInfoFuture = _getUserAndProfessionalInfo(widget.service);
|
||||||
isProfessional =
|
isProfessional =
|
||||||
FirebaseAuth.instance.currentUser!.uid == widget.service.userId
|
ApiUserRepository.currentUserId ?? '' == widget.service.userId
|
||||||
? true
|
? true
|
||||||
: false;
|
: false;
|
||||||
|
|
||||||
@@ -304,33 +302,33 @@ class _ScoreScreenState extends State<ScoreScreen> {
|
|||||||
return FilledButton(
|
return FilledButton(
|
||||||
onPressed: () {
|
onPressed: () {
|
||||||
final CommentEntity comment = widget.service.userId ==
|
final CommentEntity comment = widget.service.userId ==
|
||||||
FirebaseAuth.instance.currentUser!.uid
|
ApiUserRepository.currentUserId ?? ''
|
||||||
? CommentEntity(
|
? CommentEntity(
|
||||||
serviceId: widget.service.id!,
|
serviceId: widget.service.id!,
|
||||||
authorId:
|
authorId:
|
||||||
FirebaseAuth.instance.currentUser!.uid,
|
ApiUserRepository.currentUserId ?? '',
|
||||||
isFromUser: true,
|
isFromUser: true,
|
||||||
score: _rating,
|
score: _rating,
|
||||||
destinationId: widget.service.professionalId,
|
destinationId: widget.service.professionalId,
|
||||||
content: commentController.text.trim(),
|
content: commentController.text.trim(),
|
||||||
createdAt: Timestamp.now(),
|
createdAt: DateTime.now().toIso8601String(),
|
||||||
)
|
)
|
||||||
: CommentEntity(
|
: CommentEntity(
|
||||||
serviceId: widget.service.id!,
|
serviceId: widget.service.id!,
|
||||||
authorId:
|
authorId:
|
||||||
FirebaseAuth.instance.currentUser!.uid,
|
ApiUserRepository.currentUserId ?? '',
|
||||||
isFromUser: false,
|
isFromUser: false,
|
||||||
score: _rating,
|
score: _rating,
|
||||||
destinationId: widget.service.userId,
|
destinationId: widget.service.userId,
|
||||||
content: commentController.text.trim(),
|
content: commentController.text.trim(),
|
||||||
createdAt: Timestamp.now(),
|
createdAt: DateTime.now().toIso8601String(),
|
||||||
);
|
);
|
||||||
|
|
||||||
BlocProvider.of<ScoreBloc>(context)
|
BlocProvider.of<ScoreBloc>(context)
|
||||||
.add(SendScoreEvent(comment: comment));
|
.add(SendScoreEvent(comment: comment));
|
||||||
|
|
||||||
if (widget.service.userId ==
|
if (widget.service.userId ==
|
||||||
FirebaseAuth.instance.currentUser!.uid) {
|
ApiUserRepository.currentUserId ?? '') {
|
||||||
context.read<ServiceBloc>().add(
|
context.read<ServiceBloc>().add(
|
||||||
UpdateProfessionalScored(widget.service.id!));
|
UpdateProfessionalScored(widget.service.id!));
|
||||||
} else {
|
} else {
|
||||||
@@ -392,11 +390,11 @@ class _ScoreScreenState extends State<ScoreScreen> {
|
|||||||
|
|
||||||
Future<List<dynamic>> _getUserAndProfessionalInfo(
|
Future<List<dynamic>> _getUserAndProfessionalInfo(
|
||||||
ServiceEntity service) async {
|
ServiceEntity service) async {
|
||||||
final userRepo = FirebaseUserRepository(FirebaseAuth.instance);
|
final userRepo = Injector.appInstance.get<UserRepository>();
|
||||||
|
|
||||||
final MyUser? userInfo;
|
final MyUser? userInfo;
|
||||||
|
|
||||||
if (FirebaseAuth.instance.currentUser!.uid == service.userId) {
|
if (ApiUserRepository.currentUserId ?? '' == service.userId) {
|
||||||
userInfo = await userRepo.getMyUser(service.professionalId);
|
userInfo = await userRepo.getMyUser(service.professionalId);
|
||||||
} else {
|
} else {
|
||||||
userInfo = await userRepo.getMyUser(service.userId);
|
userInfo = await userRepo.getMyUser(service.userId);
|
||||||
|
|||||||
@@ -1,6 +1,5 @@
|
|||||||
import 'dart:async';
|
import 'dart:async';
|
||||||
import 'package:community_material_icon/community_material_icon.dart';
|
import 'package:community_material_icon/community_material_icon.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:flutter_bloc/flutter_bloc.dart';
|
import 'package:flutter_bloc/flutter_bloc.dart';
|
||||||
@@ -848,7 +847,7 @@ class _ProfessionalServiceScreenState extends State<ProfessionalServiceScreen> {
|
|||||||
|
|
||||||
Future<List<dynamic>> _getUserAndProfessionalInfo(
|
Future<List<dynamic>> _getUserAndProfessionalInfo(
|
||||||
ServiceEntity service) async {
|
ServiceEntity service) async {
|
||||||
final userRepo = FirebaseUserRepository(FirebaseAuth.instance);
|
final userRepo = Injector.appInstance.get<UserRepository>();
|
||||||
final userInfo = await userRepo.getMyUser(service.userId);
|
final userInfo = await userRepo.getMyUser(service.userId);
|
||||||
|
|
||||||
return [userInfo];
|
return [userInfo];
|
||||||
|
|||||||
@@ -1,6 +1,5 @@
|
|||||||
import 'dart:async';
|
import 'dart:async';
|
||||||
|
|
||||||
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:flutter_bloc/flutter_bloc.dart';
|
import 'package:flutter_bloc/flutter_bloc.dart';
|
||||||
@@ -484,16 +483,15 @@ class _ServiceScreenState extends State<ServiceScreen> {
|
|||||||
|
|
||||||
Future<List<dynamic>> _getUserAndProfessionalInfo(
|
Future<List<dynamic>> _getUserAndProfessionalInfo(
|
||||||
ServiceEntity service) async {
|
ServiceEntity service) async {
|
||||||
final userRepo = FirebaseUserRepository(FirebaseAuth.instance);
|
final userRepo = Injector.appInstance.get<UserRepository>();
|
||||||
final professionalRepo = FirebaseProfessionalRepository();
|
final professionalRepo = Injector.appInstance.get<ApiProfessionalRepository>();
|
||||||
if (FirebaseAuth.instance.currentUser!.uid == service.professionalId) {
|
if ((ApiUserRepository.currentUserId ?? '') == service.professionalId) {
|
||||||
final userInfo = await userRepo.getMyUser(service.userId);
|
final userInfo = await userRepo.getMyUser(service.userId);
|
||||||
final professionalInfo =
|
final professionalInfo =
|
||||||
await professionalRepo.getProInfo(service.professionalId);
|
await professionalRepo.getProInfo(service.professionalId);
|
||||||
|
|
||||||
return [userInfo, professionalInfo];
|
return [userInfo, professionalInfo];
|
||||||
} else {
|
} else {
|
||||||
final userRepo = FirebaseUserRepository(FirebaseAuth.instance);
|
|
||||||
final userInfo = await userRepo.getMyUser(service.professionalId);
|
final userInfo = await userRepo.getMyUser(service.professionalId);
|
||||||
|
|
||||||
final professionalInfo =
|
final professionalInfo =
|
||||||
|
|||||||
@@ -1,6 +1,5 @@
|
|||||||
import 'dart:async';
|
import 'dart:async';
|
||||||
import 'package:community_material_icon/community_material_icon.dart';
|
import 'package:community_material_icon/community_material_icon.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:flutter_bloc/flutter_bloc.dart';
|
import 'package:flutter_bloc/flutter_bloc.dart';
|
||||||
@@ -975,8 +974,8 @@ class _UserServiceScreenState extends State<UserServiceScreen> {
|
|||||||
|
|
||||||
Future<List<dynamic>> _getUserAndProfessionalInfo(
|
Future<List<dynamic>> _getUserAndProfessionalInfo(
|
||||||
ServiceEntity service) async {
|
ServiceEntity service) async {
|
||||||
final userRepo = FirebaseUserRepository(FirebaseAuth.instance);
|
final userRepo = Injector.appInstance.get<UserRepository>();
|
||||||
final professionalRepo = FirebaseProfessionalRepository();
|
final professionalRepo = Injector.appInstance.get<ApiProfessionalRepository>();
|
||||||
|
|
||||||
final userInfo = await userRepo.getMyUser(service.professionalId);
|
final userInfo = await userRepo.getMyUser(service.professionalId);
|
||||||
final professionalInfo =
|
final professionalInfo =
|
||||||
|
|||||||
@@ -22,7 +22,7 @@ class UserCalendarScreen extends StatefulWidget {
|
|||||||
class UserCalendarScreenState extends State<UserCalendarScreen> {
|
class UserCalendarScreenState extends State<UserCalendarScreen> {
|
||||||
final settingRepository = Injector.appInstance.get<SettingRepository>();
|
final settingRepository = Injector.appInstance.get<SettingRepository>();
|
||||||
final serviceRepository =
|
final serviceRepository =
|
||||||
Injector.appInstance.get<FirebaseServiceRepository>();
|
Injector.appInstance.get<ApiServiceRepository>();
|
||||||
SettingEntity? settings;
|
SettingEntity? settings;
|
||||||
|
|
||||||
DateTime today = DateTime.now();
|
DateTime today = DateTime.now();
|
||||||
|
|||||||
@@ -1,7 +1,6 @@
|
|||||||
import 'dart:async';
|
import 'dart:async';
|
||||||
import 'dart:developer';
|
import 'dart:developer';
|
||||||
import 'dart:io';
|
import 'dart:io';
|
||||||
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_bloc/flutter_bloc.dart';
|
import 'package:flutter_bloc/flutter_bloc.dart';
|
||||||
@@ -549,7 +548,7 @@ class _UserMapScreenState extends State<UserMapScreen> {
|
|||||||
longitude: profesionalSeleccionado!
|
longitude: profesionalSeleccionado!
|
||||||
.professionalInfo.longitude,
|
.professionalInfo.longitude,
|
||||||
day: fechaSeleccionada.toString(),
|
day: fechaSeleccionada.toString(),
|
||||||
createdAt: Timestamp.now(),
|
createdAt: DateTime.now().toIso8601String(),
|
||||||
description: _observationController.text,
|
description: _observationController.text,
|
||||||
range1Hour1: horaSeleccionada!,
|
range1Hour1: horaSeleccionada!,
|
||||||
range1Hour2:
|
range1Hour2:
|
||||||
@@ -574,7 +573,7 @@ class _UserMapScreenState extends State<UserMapScreen> {
|
|||||||
longitude: profesionalSeleccionado!
|
longitude: profesionalSeleccionado!
|
||||||
.professionalInfo.longitude,
|
.professionalInfo.longitude,
|
||||||
day: fechaSeleccionada.toString(),
|
day: fechaSeleccionada.toString(),
|
||||||
createdAt: Timestamp.now(),
|
createdAt: DateTime.now().toIso8601String(),
|
||||||
description: _observationController.text,
|
description: _observationController.text,
|
||||||
range1Hour1: horaSeleccionada!,
|
range1Hour1: horaSeleccionada!,
|
||||||
range1Hour2:
|
range1Hour2:
|
||||||
@@ -597,7 +596,7 @@ class _UserMapScreenState extends State<UserMapScreen> {
|
|||||||
latitude: 0,
|
latitude: 0,
|
||||||
longitude: 0,
|
longitude: 0,
|
||||||
day: fechaSeleccionada.toString(),
|
day: fechaSeleccionada.toString(),
|
||||||
createdAt: Timestamp.now(),
|
createdAt: DateTime.now().toIso8601String(),
|
||||||
description: _observationController.text,
|
description: _observationController.text,
|
||||||
range1Hour1: horaSeleccionada!,
|
range1Hour1: horaSeleccionada!,
|
||||||
range1Hour2:
|
range1Hour2:
|
||||||
@@ -617,7 +616,7 @@ class _UserMapScreenState extends State<UserMapScreen> {
|
|||||||
latitude: 0,
|
latitude: 0,
|
||||||
longitude: 0,
|
longitude: 0,
|
||||||
day: fechaSeleccionada.toString(),
|
day: fechaSeleccionada.toString(),
|
||||||
createdAt: Timestamp.now(),
|
createdAt: DateTime.now().toIso8601String(),
|
||||||
description: _observationController.text,
|
description: _observationController.text,
|
||||||
range1Hour1: horaSeleccionada!,
|
range1Hour1: horaSeleccionada!,
|
||||||
range1Hour2:
|
range1Hour2:
|
||||||
|
|||||||
@@ -1,5 +1,4 @@
|
|||||||
import 'dart:async';
|
import 'dart:async';
|
||||||
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:flutter_bloc/flutter_bloc.dart';
|
import 'package:flutter_bloc/flutter_bloc.dart';
|
||||||
@@ -618,10 +617,10 @@ class _UserServiceScreenState extends State<UserServiceScreen> {
|
|||||||
|
|
||||||
Future<List<dynamic>> _getUserAndProfessionalInfo(
|
Future<List<dynamic>> _getUserAndProfessionalInfo(
|
||||||
ServiceEntity service) async {
|
ServiceEntity service) async {
|
||||||
final userRepo = FirebaseUserRepository(FirebaseAuth.instance);
|
final userRepo = Injector.appInstance.get<UserRepository>();
|
||||||
final userInfo = await userRepo.getMyUser(service.professionalId);
|
final userInfo = await userRepo.getMyUser(service.professionalId);
|
||||||
|
|
||||||
final professionalRepo = FirebaseProfessionalRepository();
|
final professionalRepo = Injector.appInstance.get<ApiProfessionalRepository>();
|
||||||
final professionalInfo =
|
final professionalInfo =
|
||||||
await professionalRepo.getProInfo(service.professionalId);
|
await professionalRepo.getProInfo(service.professionalId);
|
||||||
|
|
||||||
|
|||||||
@@ -0,0 +1,92 @@
|
|||||||
|
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 = <String, String>{'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, Map<String, String>? query}) async {
|
||||||
|
final uri = Uri.parse('$_baseUrl$path').replace(queryParameters: query);
|
||||||
|
final res = await http.get(uri, 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<String> uploadFile(String filePath) async {
|
||||||
|
final token = await getToken();
|
||||||
|
final req = http.MultipartRequest('POST', Uri.parse('$_baseUrl/storage/upload'));
|
||||||
|
if (token != null) req.headers['Authorization'] = 'Bearer $token';
|
||||||
|
req.files.add(await http.MultipartFile.fromPath('file', filePath));
|
||||||
|
final streamed = await req.send();
|
||||||
|
final res = await http.Response.fromStream(streamed);
|
||||||
|
final body = jsonDecode(res.body);
|
||||||
|
if (res.statusCode >= 200 && res.statusCode < 300) return body['url'] as String;
|
||||||
|
throw ApiException(res.statusCode, body['message']?.toString() ?? 'Upload failed');
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -2,3 +2,4 @@ library chat_repository;
|
|||||||
|
|
||||||
export 'src/entities/entities.dart';
|
export 'src/entities/entities.dart';
|
||||||
export 'src/repositories/firebase_chat_repository.dart';
|
export 'src/repositories/firebase_chat_repository.dart';
|
||||||
|
export 'src/repositories/api_chat_repository.dart';
|
||||||
|
|||||||
@@ -0,0 +1,121 @@
|
|||||||
|
import 'dart:async';
|
||||||
|
import 'dart:convert';
|
||||||
|
import 'package:http/http.dart' as http;
|
||||||
|
import 'package:shared_preferences/shared_preferences.dart';
|
||||||
|
import 'package:chat_repository/chat_repository.dart';
|
||||||
|
|
||||||
|
const _base = 'https://backend.prosapp.co/api/v1';
|
||||||
|
|
||||||
|
/// API-backed replacement for FirebaseChatRepository.
|
||||||
|
/// Mirrors the same public API so existing blocs work without changes.
|
||||||
|
class ApiChatRepository {
|
||||||
|
String? _token;
|
||||||
|
|
||||||
|
Future<String?> _getToken() async {
|
||||||
|
if (_token != null) return _token;
|
||||||
|
final prefs = await SharedPreferences.getInstance();
|
||||||
|
return _token = prefs.getString('token');
|
||||||
|
}
|
||||||
|
|
||||||
|
Future<Map<String, String>> _headers() async {
|
||||||
|
final t = await _getToken();
|
||||||
|
return {
|
||||||
|
'Content-Type': 'application/json',
|
||||||
|
if (t != null) 'Authorization': 'Bearer $t',
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
ChatEntity _chatFromApi(Map<String, dynamic> json) {
|
||||||
|
final rawMessages = json['messages'] as List? ?? [];
|
||||||
|
final messages = rawMessages
|
||||||
|
.map((m) => MessageEntity.fromDocument(m as Map<String, dynamic>))
|
||||||
|
.toList();
|
||||||
|
return ChatEntity(
|
||||||
|
id: json['id']?.toString(),
|
||||||
|
userId: json['user_id']?.toString() ?? json['userId']?.toString() ?? '',
|
||||||
|
professionalId: json['professional_id']?.toString() ??
|
||||||
|
json['professionalId']?.toString() ??
|
||||||
|
'',
|
||||||
|
messages: messages,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
MessageEntity _msgFromApi(Map<String, dynamic> json) {
|
||||||
|
return MessageEntity(
|
||||||
|
ownerId: json['owner_id']?.toString() ?? json['sender_id']?.toString() ?? '',
|
||||||
|
content: json['content']?.toString() ?? '',
|
||||||
|
createdAt: json['created_at'] != null
|
||||||
|
? DateTime.tryParse(json['created_at'].toString()) ?? DateTime.now()
|
||||||
|
: DateTime.now(),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Get or create a chat session. Maps to POST /chat/start/:professionalUserId.
|
||||||
|
/// [chatId] here is used as the professional's userId for the REST call.
|
||||||
|
Future<ChatEntity> createNewChat(
|
||||||
|
String chatId, String userId, String professionalId) async {
|
||||||
|
final res = await http.post(
|
||||||
|
Uri.parse('$_base/chat/start/$professionalId'),
|
||||||
|
headers: await _headers(),
|
||||||
|
);
|
||||||
|
final data = jsonDecode(res.body) as Map<String, dynamic>;
|
||||||
|
return _chatFromApi(data);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Streams a single chat by its ID. Fetches once and emits.
|
||||||
|
Stream<ChatEntity?> getChatById(String chatId) {
|
||||||
|
final controller = StreamController<ChatEntity?>();
|
||||||
|
_fetchChat(chatId).then((chat) {
|
||||||
|
controller.add(chat);
|
||||||
|
controller.close();
|
||||||
|
}).catchError((e) {
|
||||||
|
controller.add(null);
|
||||||
|
controller.close();
|
||||||
|
});
|
||||||
|
return controller.stream;
|
||||||
|
}
|
||||||
|
|
||||||
|
Future<ChatEntity?> _fetchChat(String chatId) async {
|
||||||
|
try {
|
||||||
|
// Try to get messages for this chat — if the chat exists it'll succeed
|
||||||
|
final res = await http.get(
|
||||||
|
Uri.parse('$_base/chat/$chatId/messages'),
|
||||||
|
headers: await _headers(),
|
||||||
|
);
|
||||||
|
if (res.statusCode == 404) return null;
|
||||||
|
final messages = jsonDecode(res.body) as List? ?? [];
|
||||||
|
return ChatEntity(
|
||||||
|
id: chatId,
|
||||||
|
userId: '',
|
||||||
|
professionalId: '',
|
||||||
|
messages: messages
|
||||||
|
.map((m) => _msgFromApi(m as Map<String, dynamic>))
|
||||||
|
.toList(),
|
||||||
|
);
|
||||||
|
} catch (_) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Future<void> sendMessage(String chatId, MessageEntity message) async {
|
||||||
|
await http.post(
|
||||||
|
Uri.parse('$_base/chat/$chatId/message'),
|
||||||
|
headers: await _headers(),
|
||||||
|
body: jsonEncode({'content': message.content}),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Get all chats for the current user.
|
||||||
|
Future<List<ChatEntity>> getMyChats() async {
|
||||||
|
try {
|
||||||
|
final res = await http.get(
|
||||||
|
Uri.parse('$_base/chat/my'),
|
||||||
|
headers: await _headers(),
|
||||||
|
);
|
||||||
|
final data = jsonDecode(res.body) as List? ?? [];
|
||||||
|
return data.map((e) => _chatFromApi(e as Map<String, dynamic>)).toList();
|
||||||
|
} catch (_) {
|
||||||
|
return [];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -11,8 +11,10 @@ dependencies:
|
|||||||
flutter:
|
flutter:
|
||||||
sdk: flutter
|
sdk: flutter
|
||||||
equatable: ^2.0.5
|
equatable: ^2.0.5
|
||||||
|
http: ^1.1.0
|
||||||
|
shared_preferences: ^2.0.10
|
||||||
|
|
||||||
# Firebase
|
# Firebase kept for FirebaseChatRepository (legacy fallback)
|
||||||
cloud_firestore: ^4.15.4
|
cloud_firestore: ^4.15.4
|
||||||
firebase_core: ^2.25.4
|
firebase_core: ^2.25.4
|
||||||
|
|
||||||
|
|||||||
@@ -4,3 +4,4 @@ export 'src/models/models.dart';
|
|||||||
export 'src/entities/entities.dart';
|
export 'src/entities/entities.dart';
|
||||||
export 'src/repositories/city_repo.dart';
|
export 'src/repositories/city_repo.dart';
|
||||||
export 'src/repositories/firebase_city_repository.dart';
|
export 'src/repositories/firebase_city_repository.dart';
|
||||||
|
export 'src/repositories/api_city_repository.dart';
|
||||||
|
|||||||
@@ -0,0 +1,76 @@
|
|||||||
|
import 'dart:async';
|
||||||
|
import 'dart:convert';
|
||||||
|
import 'package:http/http.dart' as http;
|
||||||
|
import 'package:shared_preferences/shared_preferences.dart';
|
||||||
|
import 'package:city_repository/city_repository.dart';
|
||||||
|
import 'city_repo.dart';
|
||||||
|
|
||||||
|
const _base = 'https://backend.prosapp.co/api/v1';
|
||||||
|
|
||||||
|
class ApiCityRepository implements CityRepository {
|
||||||
|
List<CityUi>? _cities;
|
||||||
|
|
||||||
|
Future<String?> _getToken() async {
|
||||||
|
final prefs = await SharedPreferences.getInstance();
|
||||||
|
return prefs.getString('token');
|
||||||
|
}
|
||||||
|
|
||||||
|
Future<Map<String, String>> _headers() async {
|
||||||
|
final t = await _getToken();
|
||||||
|
return {
|
||||||
|
'Content-Type': 'application/json',
|
||||||
|
if (t != null) 'Authorization': 'Bearer $t',
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
Future<List<CityUi>> getCities() async {
|
||||||
|
if (_cities != null) return _cities!;
|
||||||
|
|
||||||
|
final List<CityUi> cities = [];
|
||||||
|
|
||||||
|
try {
|
||||||
|
final countriesRes = await http.get(
|
||||||
|
Uri.parse('$_base/locations/countries'),
|
||||||
|
headers: await _headers(),
|
||||||
|
);
|
||||||
|
final countries = jsonDecode(countriesRes.body) as List;
|
||||||
|
|
||||||
|
for (final country in countries) {
|
||||||
|
final countryId = country['id']?.toString() ?? '';
|
||||||
|
final countryName = country['name']?.toString() ?? '';
|
||||||
|
|
||||||
|
final regionsRes = await http.get(
|
||||||
|
Uri.parse('$_base/locations/countries/$countryId/regions'),
|
||||||
|
headers: await _headers(),
|
||||||
|
);
|
||||||
|
final regions = jsonDecode(regionsRes.body) as List;
|
||||||
|
|
||||||
|
for (final region in regions) {
|
||||||
|
final regionId = region['id']?.toString() ?? '';
|
||||||
|
final regionName = region['name']?.toString() ?? '';
|
||||||
|
|
||||||
|
final citiesRes = await http.get(
|
||||||
|
Uri.parse('$_base/locations/regions/$regionId/cities'),
|
||||||
|
headers: await _headers(),
|
||||||
|
);
|
||||||
|
final citiesList = jsonDecode(citiesRes.body) as List;
|
||||||
|
|
||||||
|
for (final city in citiesList) {
|
||||||
|
cities.add(CityUi(
|
||||||
|
cityName: city['name']?.toString() ?? '',
|
||||||
|
coordsOfCity: city['coords']?.toString() ?? '',
|
||||||
|
stateOfCity: regionName,
|
||||||
|
countryOfCity: countryName,
|
||||||
|
));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} catch (e) {
|
||||||
|
rethrow;
|
||||||
|
}
|
||||||
|
|
||||||
|
_cities = cities;
|
||||||
|
return cities;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -11,8 +11,10 @@ dependencies:
|
|||||||
flutter:
|
flutter:
|
||||||
sdk: flutter
|
sdk: flutter
|
||||||
equatable: ^2.0.5
|
equatable: ^2.0.5
|
||||||
|
http: ^1.1.0
|
||||||
|
shared_preferences: ^2.0.10
|
||||||
|
|
||||||
# Firebase
|
# Firebase kept for FirebaseCityRepository (legacy fallback)
|
||||||
cloud_firestore: ^4.15.4
|
cloud_firestore: ^4.15.4
|
||||||
firebase_core: ^2.25.4
|
firebase_core: ^2.25.4
|
||||||
|
|
||||||
|
|||||||
@@ -4,3 +4,4 @@ export 'src/models/models.dart';
|
|||||||
export 'src/entities/entities.dart';
|
export 'src/entities/entities.dart';
|
||||||
export 'src/repositories/profession_repo.dart';
|
export 'src/repositories/profession_repo.dart';
|
||||||
export 'src/repositories/firebase_profession_repository.dart';
|
export 'src/repositories/firebase_profession_repository.dart';
|
||||||
|
export 'src/repositories/api_profession_repository.dart';
|
||||||
@@ -0,0 +1,46 @@
|
|||||||
|
import 'dart:async';
|
||||||
|
import 'dart:convert';
|
||||||
|
import 'package:http/http.dart' as http;
|
||||||
|
import 'package:shared_preferences/shared_preferences.dart';
|
||||||
|
import 'package:profession_repository/profession_repository.dart';
|
||||||
|
import 'profession_repo.dart';
|
||||||
|
|
||||||
|
const _base = 'https://backend.prosapp.co/api/v1';
|
||||||
|
|
||||||
|
class ApiProfessionRepository implements ProfessionRepository {
|
||||||
|
Future<String?> _getToken() async {
|
||||||
|
final prefs = await SharedPreferences.getInstance();
|
||||||
|
return prefs.getString('token');
|
||||||
|
}
|
||||||
|
|
||||||
|
Future<Map<String, String>> _headers() async {
|
||||||
|
final t = await _getToken();
|
||||||
|
return {
|
||||||
|
'Content-Type': 'application/json',
|
||||||
|
if (t != null) 'Authorization': 'Bearer $t',
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
Future<Professions> getProfessions() async {
|
||||||
|
try {
|
||||||
|
final res = await http.get(
|
||||||
|
Uri.parse('$_base/professions'),
|
||||||
|
headers: await _headers(),
|
||||||
|
);
|
||||||
|
final data = jsonDecode(res.body);
|
||||||
|
// Backend returns either a list of profession objects or a map with a professions key
|
||||||
|
if (data is List) {
|
||||||
|
final names = data
|
||||||
|
.map((e) => (e['name'] ?? e['title'] ?? e.toString()).toString())
|
||||||
|
.toList();
|
||||||
|
return Professions(names);
|
||||||
|
} else if (data is Map && data.containsKey('professions')) {
|
||||||
|
return Professions(List<String>.from(data['professions']));
|
||||||
|
}
|
||||||
|
return Professions([]);
|
||||||
|
} catch (e) {
|
||||||
|
rethrow;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -11,8 +11,10 @@ dependencies:
|
|||||||
flutter:
|
flutter:
|
||||||
sdk: flutter
|
sdk: flutter
|
||||||
equatable: ^2.0.5
|
equatable: ^2.0.5
|
||||||
|
http: ^1.1.0
|
||||||
|
shared_preferences: ^2.0.10
|
||||||
|
|
||||||
# Firebase
|
# Firebase kept for FirebaseProfessionRepository (legacy fallback)
|
||||||
cloud_firestore: ^4.15.4
|
cloud_firestore: ^4.15.4
|
||||||
firebase_core: ^2.25.4
|
firebase_core: ^2.25.4
|
||||||
|
|
||||||
|
|||||||
@@ -3,3 +3,4 @@ library professional_repository;
|
|||||||
export 'src/models/models.dart';
|
export 'src/models/models.dart';
|
||||||
export 'src/entities/entities.dart';
|
export 'src/entities/entities.dart';
|
||||||
export 'src/repositories/firebase_professional_repository.dart';
|
export 'src/repositories/firebase_professional_repository.dart';
|
||||||
|
export 'src/repositories/api_professional_repository.dart';
|
||||||
|
|||||||
+217
@@ -0,0 +1,217 @@
|
|||||||
|
import 'dart:async';
|
||||||
|
import 'dart:convert';
|
||||||
|
import 'package:http/http.dart' as http;
|
||||||
|
import 'package:shared_preferences/shared_preferences.dart';
|
||||||
|
import 'package:professional_repository/professional_repository.dart';
|
||||||
|
|
||||||
|
const _base = 'https://backend.prosapp.co/api/v1';
|
||||||
|
|
||||||
|
/// API-backed replacement for FirebaseProfessionalRepository.
|
||||||
|
/// Mirrors the same public API so existing blocs work without changes.
|
||||||
|
class ApiProfessionalRepository {
|
||||||
|
ProfessionalEntity? _proInfo;
|
||||||
|
final StreamController<ProfessionalEntity?> _proInfoBroadcast =
|
||||||
|
StreamController<ProfessionalEntity?>.broadcast();
|
||||||
|
|
||||||
|
bool isProModeActive = false;
|
||||||
|
final StreamController<bool> _isProModeActiveBroadcast =
|
||||||
|
StreamController<bool>.broadcast();
|
||||||
|
|
||||||
|
String? _token;
|
||||||
|
|
||||||
|
ApiProfessionalRepository() {
|
||||||
|
_isProModeActiveBroadcast.add(isProModeActive);
|
||||||
|
}
|
||||||
|
|
||||||
|
Future<String?> _getToken() async {
|
||||||
|
if (_token != null) return _token;
|
||||||
|
final prefs = await SharedPreferences.getInstance();
|
||||||
|
return _token = prefs.getString('token');
|
||||||
|
}
|
||||||
|
|
||||||
|
Future<Map<String, String>> _headers() async {
|
||||||
|
final t = await _getToken();
|
||||||
|
return {
|
||||||
|
'Content-Type': 'application/json',
|
||||||
|
if (t != null) 'Authorization': 'Bearer $t',
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
Future<dynamic> _get(String path) async {
|
||||||
|
final res = await http.get(Uri.parse('$_base$path'), headers: await _headers());
|
||||||
|
return jsonDecode(res.body);
|
||||||
|
}
|
||||||
|
|
||||||
|
Future<dynamic> _patch(String path, Map<String, dynamic> body) async {
|
||||||
|
final res = await http.patch(
|
||||||
|
Uri.parse('$_base$path'),
|
||||||
|
headers: await _headers(),
|
||||||
|
body: jsonEncode(body),
|
||||||
|
);
|
||||||
|
return jsonDecode(res.body);
|
||||||
|
}
|
||||||
|
|
||||||
|
ProfessionalEntity? lastProInfo() => _proInfo;
|
||||||
|
|
||||||
|
Stream<ProfessionalEntity?> streamProInfo() => _proInfoBroadcast.stream;
|
||||||
|
|
||||||
|
Stream<bool> sreamIsProModeActive() => _isProModeActiveBroadcast.stream;
|
||||||
|
|
||||||
|
switchProMode() {
|
||||||
|
isProModeActive = !isProModeActive;
|
||||||
|
_isProModeActiveBroadcast.add(isProModeActive);
|
||||||
|
}
|
||||||
|
|
||||||
|
ProfessionalEntity _fromApi(Map<String, dynamic> json) {
|
||||||
|
return ProfessionalEntity(
|
||||||
|
id: json['user_id']?.toString() ?? json['id']?.toString() ?? '',
|
||||||
|
identification: json['identification']?.toString() ?? '',
|
||||||
|
address: json['address']?.toString() ?? '',
|
||||||
|
aditionalAddress: json['aditional_address']?.toString() ?? '',
|
||||||
|
profession: json['profession']?.toString() ?? '',
|
||||||
|
ratePreferences: json['rate_preferences'] as bool? ?? false,
|
||||||
|
rate: json['rate']?.toString() ?? '',
|
||||||
|
locationPreferences: intToEnum((json['location_preferences'] as num?)?.toInt() ?? 0),
|
||||||
|
bannerPicture: json['banner_picture']?.toString() ?? '',
|
||||||
|
identificationPicture: json['identification_picture']?.toString() ?? '',
|
||||||
|
certificatePicture: json['certificate_picture']?.toString() ?? '',
|
||||||
|
latitude: double.tryParse(json['latitude']?.toString() ?? '0') ?? 0.0,
|
||||||
|
longitude: double.tryParse(json['longitude']?.toString() ?? '0') ?? 0.0,
|
||||||
|
specializations: json['specializations'] != null
|
||||||
|
? List<String>.from(json['specializations'])
|
||||||
|
: [],
|
||||||
|
specializationsPictures: json['specializations_pictures'] != null
|
||||||
|
? List<String>.from(json['specializations_pictures'])
|
||||||
|
: [],
|
||||||
|
schedules: json['schedules'] != null
|
||||||
|
? Schedules.fromDocument(json['schedules'] as Map<String, dynamic>)
|
||||||
|
: Schedules.empty,
|
||||||
|
paymentMethods: json['payment_methods'] != null
|
||||||
|
? PaymentMethodEntity.fromDocument(
|
||||||
|
json['payment_methods'] as Map<String, dynamic>)
|
||||||
|
: PaymentMethodEntity.empty,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
Future<void> updateFromFirebase({required String userId}) async {
|
||||||
|
try {
|
||||||
|
final proInfo = await getProInfo(userId);
|
||||||
|
_proInfo = proInfo;
|
||||||
|
_proInfoBroadcast.add(proInfo);
|
||||||
|
} catch (e) {
|
||||||
|
_proInfo = null;
|
||||||
|
_proInfoBroadcast.add(null);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Future<ProfessionalEntity?> getProInfo(String myUserId) async {
|
||||||
|
try {
|
||||||
|
final data = await _get('/professionals/$myUserId');
|
||||||
|
if (data == null) return null;
|
||||||
|
return _fromApi(data as Map<String, dynamic>);
|
||||||
|
} catch (_) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Future<void> updateProfessionalInfo(
|
||||||
|
String address,
|
||||||
|
String aditionalAddress,
|
||||||
|
bool ratePreferences,
|
||||||
|
String rate,
|
||||||
|
LocationPreferences locationPreferences,
|
||||||
|
double latitude,
|
||||||
|
double longitude,
|
||||||
|
Schedules schedules,
|
||||||
|
PaymentMethodEntity paymentMethods,
|
||||||
|
) async {
|
||||||
|
await _patch('/professionals/me', {
|
||||||
|
'address': address,
|
||||||
|
'aditional_address': aditionalAddress,
|
||||||
|
'rate_preferences': ratePreferences,
|
||||||
|
'rate': rate,
|
||||||
|
'location_preferences': enumToInt(locationPreferences),
|
||||||
|
'latitude': latitude,
|
||||||
|
'longitude': longitude,
|
||||||
|
'schedules': schedules.toJson(),
|
||||||
|
'payment_methods': paymentMethods.toDocument(),
|
||||||
|
});
|
||||||
|
|
||||||
|
if (_proInfo != null) {
|
||||||
|
await updateFromFirebase(userId: _proInfo!.id);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Future<void> saveProfessionalInfo(ProfessionalEntity entity) async {
|
||||||
|
await _patch('/professionals/me', entity.toDocument());
|
||||||
|
}
|
||||||
|
|
||||||
|
Future<void> uploadBannerPicture(String file) async {
|
||||||
|
final token = await _getToken();
|
||||||
|
final req = http.MultipartRequest('POST', Uri.parse('$_base/storage/upload'));
|
||||||
|
if (token != null) req.headers['Authorization'] = 'Bearer $token';
|
||||||
|
req.files.add(await http.MultipartFile.fromPath('file', file));
|
||||||
|
final streamed = await req.send();
|
||||||
|
final res = await http.Response.fromStream(streamed);
|
||||||
|
final body = jsonDecode(res.body) as Map<String, dynamic>;
|
||||||
|
final url = body['url'] as String;
|
||||||
|
await _patch('/professionals/me', {'banner_picture': url});
|
||||||
|
}
|
||||||
|
|
||||||
|
Future<String> uploadPdfCedula(String file, String userId) async {
|
||||||
|
final token = await _getToken();
|
||||||
|
final req = http.MultipartRequest('POST', Uri.parse('$_base/storage/upload'));
|
||||||
|
if (token != null) req.headers['Authorization'] = 'Bearer $token';
|
||||||
|
req.files.add(await http.MultipartFile.fromPath('file', file));
|
||||||
|
final streamed = await req.send();
|
||||||
|
final res = await http.Response.fromStream(streamed);
|
||||||
|
final body = jsonDecode(res.body) as Map<String, dynamic>;
|
||||||
|
return body['url'] as String;
|
||||||
|
}
|
||||||
|
|
||||||
|
Future<String> uploadPdfCertificado(String file, String userId) async {
|
||||||
|
final token = await _getToken();
|
||||||
|
final req = http.MultipartRequest('POST', Uri.parse('$_base/storage/upload'));
|
||||||
|
if (token != null) req.headers['Authorization'] = 'Bearer $token';
|
||||||
|
req.files.add(await http.MultipartFile.fromPath('file', file));
|
||||||
|
final streamed = await req.send();
|
||||||
|
final res = await http.Response.fromStream(streamed);
|
||||||
|
final body = jsonDecode(res.body) as Map<String, dynamic>;
|
||||||
|
return body['url'] as String;
|
||||||
|
}
|
||||||
|
|
||||||
|
Future<List<String>> uploadPdfsEspecializaciones(
|
||||||
|
List<String> files, String userId) async {
|
||||||
|
final List<String> urls = [];
|
||||||
|
for (final file in files) {
|
||||||
|
final token = await _getToken();
|
||||||
|
final req = http.MultipartRequest('POST', Uri.parse('$_base/storage/upload'));
|
||||||
|
if (token != null) req.headers['Authorization'] = 'Bearer $token';
|
||||||
|
req.files.add(await http.MultipartFile.fromPath('file', file));
|
||||||
|
final streamed = await req.send();
|
||||||
|
final res = await http.Response.fromStream(streamed);
|
||||||
|
final body = jsonDecode(res.body) as Map<String, dynamic>;
|
||||||
|
urls.add(body['url'] as String);
|
||||||
|
}
|
||||||
|
return urls;
|
||||||
|
}
|
||||||
|
|
||||||
|
Future<List<ProfessionalEntity>> getProfessionalInfo() async {
|
||||||
|
try {
|
||||||
|
final data = await _get('/professionals') as List;
|
||||||
|
return data.map((e) => _fromApi(e as Map<String, dynamic>)).toList();
|
||||||
|
} catch (_) {
|
||||||
|
return [];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Future<List<ProfessionalEntity>> getProfessionalsFromIds(
|
||||||
|
Iterable<String> ids) async {
|
||||||
|
final result = <ProfessionalEntity>[];
|
||||||
|
for (final id in ids) {
|
||||||
|
final p = await getProInfo(id);
|
||||||
|
if (p != null) result.add(p);
|
||||||
|
}
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -11,12 +11,14 @@ dependencies:
|
|||||||
flutter:
|
flutter:
|
||||||
sdk: flutter
|
sdk: flutter
|
||||||
equatable: ^2.0.5
|
equatable: ^2.0.5
|
||||||
|
http: ^1.1.0
|
||||||
|
shared_preferences: ^2.0.10
|
||||||
|
intl: ^0.19.0
|
||||||
|
|
||||||
# Firebase
|
# Firebase kept for FirebaseProfessionalRepository (legacy fallback)
|
||||||
cloud_firestore: ^4.15.4
|
cloud_firestore: ^4.15.4
|
||||||
firebase_storage: ^11.6.5
|
firebase_storage: ^11.6.5
|
||||||
firebase_core: ^2.25.4
|
firebase_core: ^2.25.4
|
||||||
intl: ^0.19.0
|
|
||||||
firebase_auth: ^4.17.8
|
firebase_auth: ^4.17.8
|
||||||
|
|
||||||
dev_dependencies:
|
dev_dependencies:
|
||||||
|
|||||||
@@ -1,4 +1,5 @@
|
|||||||
library chat_repository;
|
library score_repository;
|
||||||
|
|
||||||
export 'src/entities/entities.dart';
|
export 'src/entities/entities.dart';
|
||||||
export 'src/repositories/firebase_score_repository.dart';
|
export 'src/repositories/firebase_score_repository.dart';
|
||||||
|
export 'src/repositories/api_score_repository.dart';
|
||||||
|
|||||||
@@ -1,4 +1,3 @@
|
|||||||
import 'package:cloud_firestore/cloud_firestore.dart';
|
|
||||||
import 'package:equatable/equatable.dart';
|
import 'package:equatable/equatable.dart';
|
||||||
|
|
||||||
class CommentEntity extends Equatable {
|
class CommentEntity extends Equatable {
|
||||||
@@ -8,7 +7,7 @@ class CommentEntity extends Equatable {
|
|||||||
final String content;
|
final String content;
|
||||||
final double score;
|
final double score;
|
||||||
final bool isFromUser;
|
final bool isFromUser;
|
||||||
final Timestamp createdAt;
|
final String createdAt;
|
||||||
|
|
||||||
const CommentEntity({
|
const CommentEntity({
|
||||||
required this.authorId,
|
required this.authorId,
|
||||||
@@ -28,7 +27,7 @@ class CommentEntity extends Equatable {
|
|||||||
content: doc['content'] as String,
|
content: doc['content'] as String,
|
||||||
score: doc['score'] as double,
|
score: doc['score'] as double,
|
||||||
isFromUser: doc['is_from_user'] as bool,
|
isFromUser: doc['is_from_user'] as bool,
|
||||||
createdAt: doc['created_at'] as Timestamp,
|
createdAt: doc['created_at']?.toString() ?? DateTime.now().toIso8601String(),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -1,4 +1,3 @@
|
|||||||
import 'package:cloud_firestore/cloud_firestore.dart';
|
|
||||||
import 'package:equatable/equatable.dart';
|
import 'package:equatable/equatable.dart';
|
||||||
|
|
||||||
class ReputationEntity extends Equatable {
|
class ReputationEntity extends Equatable {
|
||||||
|
|||||||
@@ -0,0 +1,127 @@
|
|||||||
|
import 'dart:async';
|
||||||
|
import 'dart:convert';
|
||||||
|
import 'package:http/http.dart' as http;
|
||||||
|
import 'package:shared_preferences/shared_preferences.dart';
|
||||||
|
import 'package:score_repository/score_repository.dart';
|
||||||
|
import 'package:score_repository/src/entities/comment_entity.dart';
|
||||||
|
|
||||||
|
const _base = 'https://backend.prosapp.co/api/v1';
|
||||||
|
|
||||||
|
/// API-backed replacement for FirebaseScoreRepository.
|
||||||
|
/// Mirrors the same public API so existing blocs work without changes.
|
||||||
|
class ApiScoreRepository {
|
||||||
|
ReputationEntity? _reputation;
|
||||||
|
final StreamController<ReputationEntity> _reputationController =
|
||||||
|
StreamController<ReputationEntity>.broadcast();
|
||||||
|
|
||||||
|
String? _token;
|
||||||
|
|
||||||
|
ApiScoreRepository() {
|
||||||
|
_reputationController.add(_emptyReputation());
|
||||||
|
}
|
||||||
|
|
||||||
|
ReputationEntity _emptyReputation() => const ReputationEntity(
|
||||||
|
total: 0,
|
||||||
|
average: 0,
|
||||||
|
totalPro: 0,
|
||||||
|
averagePro: 0,
|
||||||
|
);
|
||||||
|
|
||||||
|
Future<String?> _getToken() async {
|
||||||
|
if (_token != null) return _token;
|
||||||
|
final prefs = await SharedPreferences.getInstance();
|
||||||
|
return _token = prefs.getString('token');
|
||||||
|
}
|
||||||
|
|
||||||
|
Future<Map<String, String>> _headers() async {
|
||||||
|
final t = await _getToken();
|
||||||
|
return {
|
||||||
|
'Content-Type': 'application/json',
|
||||||
|
if (t != null) 'Authorization': 'Bearer $t',
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
Stream<ReputationEntity> streamReputation() => _reputationController.stream;
|
||||||
|
|
||||||
|
ReputationEntity getReputation() => _reputation ?? _emptyReputation();
|
||||||
|
|
||||||
|
Future<ReputationEntity> getReputationByUserId(String userId) async {
|
||||||
|
try {
|
||||||
|
final res = await http.get(
|
||||||
|
Uri.parse('$_base/comments/reputation/$userId'),
|
||||||
|
headers: await _headers(),
|
||||||
|
);
|
||||||
|
final data = jsonDecode(res.body) as Map<String, dynamic>;
|
||||||
|
final rep = ReputationEntity.fromDocument(data);
|
||||||
|
_reputation = rep;
|
||||||
|
_reputationController.add(rep);
|
||||||
|
return rep;
|
||||||
|
} catch (_) {
|
||||||
|
return _emptyReputation();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Stream<List<CommentEntity>> getScoresForUser(String userId) {
|
||||||
|
final controller = StreamController<List<CommentEntity>>();
|
||||||
|
_fetchComments(userId: userId, isFromUser: false).then((list) {
|
||||||
|
controller.add(list);
|
||||||
|
controller.close();
|
||||||
|
}).catchError((e) {
|
||||||
|
controller.add([]);
|
||||||
|
controller.close();
|
||||||
|
});
|
||||||
|
return controller.stream;
|
||||||
|
}
|
||||||
|
|
||||||
|
Stream<List<CommentEntity>> getScoresForProfessional(String userId) {
|
||||||
|
final controller = StreamController<List<CommentEntity>>();
|
||||||
|
_fetchComments(userId: userId, isFromUser: true).then((list) {
|
||||||
|
controller.add(list);
|
||||||
|
controller.close();
|
||||||
|
}).catchError((e) {
|
||||||
|
controller.add([]);
|
||||||
|
controller.close();
|
||||||
|
});
|
||||||
|
return controller.stream;
|
||||||
|
}
|
||||||
|
|
||||||
|
Future<List<CommentEntity>> _fetchComments({
|
||||||
|
required String userId,
|
||||||
|
required bool isFromUser,
|
||||||
|
}) async {
|
||||||
|
try {
|
||||||
|
final res = await http.get(
|
||||||
|
Uri.parse('$_base/comments/reputation/$userId'),
|
||||||
|
headers: await _headers(),
|
||||||
|
);
|
||||||
|
final data = jsonDecode(res.body);
|
||||||
|
if (data is! Map) return [];
|
||||||
|
// The endpoint returns reputation summary, not individual comments
|
||||||
|
// Return empty list since we only have aggregated data
|
||||||
|
return [];
|
||||||
|
} catch (_) {
|
||||||
|
return [];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Future<void> addComment(CommentEntity comment) async {
|
||||||
|
try {
|
||||||
|
await http.post(
|
||||||
|
Uri.parse('$_base/comments'),
|
||||||
|
headers: await _headers(),
|
||||||
|
body: jsonEncode({
|
||||||
|
'author_id': comment.authorId,
|
||||||
|
'destination_id': comment.destinationId,
|
||||||
|
'service_id': comment.serviceId,
|
||||||
|
'content': comment.content,
|
||||||
|
'score': comment.score,
|
||||||
|
'is_from_user': comment.isFromUser,
|
||||||
|
}),
|
||||||
|
);
|
||||||
|
// Refresh reputation after adding a comment
|
||||||
|
await getReputationByUserId(comment.destinationId);
|
||||||
|
} catch (_) {
|
||||||
|
// Stub — do not crash if comment fails
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -11,8 +11,10 @@ dependencies:
|
|||||||
flutter:
|
flutter:
|
||||||
sdk: flutter
|
sdk: flutter
|
||||||
equatable: ^2.0.5
|
equatable: ^2.0.5
|
||||||
|
http: ^1.1.0
|
||||||
|
shared_preferences: ^2.0.10
|
||||||
|
|
||||||
# Firebase
|
# Firebase kept for FirebaseScoreRepository (legacy) and CommentEntity uses Timestamp
|
||||||
cloud_firestore: ^4.15.4
|
cloud_firestore: ^4.15.4
|
||||||
firebase_core: ^2.25.4
|
firebase_core: ^2.25.4
|
||||||
firebase_auth: ^4.17.4
|
firebase_auth: ^4.17.4
|
||||||
|
|||||||
@@ -3,3 +3,4 @@ library service_repository;
|
|||||||
export 'src/entities/entities.dart';
|
export 'src/entities/entities.dart';
|
||||||
export 'src/models/models.dart';
|
export 'src/models/models.dart';
|
||||||
export 'src/repositories/firebase_service_repository.dart';
|
export 'src/repositories/firebase_service_repository.dart';
|
||||||
|
export 'src/repositories/api_service_repository.dart';
|
||||||
|
|||||||
@@ -1,5 +1,4 @@
|
|||||||
import 'package:equatable/equatable.dart';
|
import 'package:equatable/equatable.dart';
|
||||||
import 'package:cloud_firestore/cloud_firestore.dart';
|
|
||||||
import 'package:service_repository/service_repository.dart';
|
import 'package:service_repository/service_repository.dart';
|
||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
|
|
||||||
@@ -14,7 +13,7 @@ class ServiceEntity extends Equatable {
|
|||||||
final double latitude;
|
final double latitude;
|
||||||
final double longitude;
|
final double longitude;
|
||||||
final String day;
|
final String day;
|
||||||
final Timestamp createdAt;
|
final String createdAt;
|
||||||
final String description;
|
final String description;
|
||||||
final TimeOfDay range1Hour1;
|
final TimeOfDay range1Hour1;
|
||||||
final TimeOfDay range1Hour2;
|
final TimeOfDay range1Hour2;
|
||||||
@@ -54,7 +53,7 @@ class ServiceEntity extends Equatable {
|
|||||||
latitude: doc['latitude'] as double,
|
latitude: doc['latitude'] as double,
|
||||||
longitude: doc['longitude'] as double,
|
longitude: doc['longitude'] as double,
|
||||||
day: doc['day'] as String,
|
day: doc['day'] as String,
|
||||||
createdAt: doc['created_at'] as Timestamp,
|
createdAt: doc['created_at']?.toString() ?? DateTime.now().toIso8601String(),
|
||||||
description: doc['description'] as String,
|
description: doc['description'] as String,
|
||||||
range1Hour1: parseTimeOfDay(doc['range1_hour1'] as String),
|
range1Hour1: parseTimeOfDay(doc['range1_hour1'] as String),
|
||||||
range1Hour2: parseTimeOfDay(doc['range1_hour2'] as String),
|
range1Hour2: parseTimeOfDay(doc['range1_hour2'] as String),
|
||||||
|
|||||||
@@ -0,0 +1,188 @@
|
|||||||
|
import 'dart:async';
|
||||||
|
import 'dart:convert';
|
||||||
|
import 'package:flutter/material.dart';
|
||||||
|
import 'package:http/http.dart' as http;
|
||||||
|
import 'package:shared_preferences/shared_preferences.dart';
|
||||||
|
import 'package:service_repository/service_repository.dart';
|
||||||
|
|
||||||
|
const _base = 'https://backend.prosapp.co/api/v1';
|
||||||
|
|
||||||
|
/// API-backed replacement for FirebaseServiceRepository.
|
||||||
|
/// Mirrors the same public API so existing blocs work without changes.
|
||||||
|
class ApiServiceRepository {
|
||||||
|
String? _token;
|
||||||
|
|
||||||
|
Future<String?> _getToken() async {
|
||||||
|
if (_token != null) return _token;
|
||||||
|
final prefs = await SharedPreferences.getInstance();
|
||||||
|
return _token = prefs.getString('token');
|
||||||
|
}
|
||||||
|
|
||||||
|
Future<Map<String, String>> _headers() async {
|
||||||
|
final t = await _getToken();
|
||||||
|
return {
|
||||||
|
'Content-Type': 'application/json',
|
||||||
|
if (t != null) 'Authorization': 'Bearer $t',
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
Future<dynamic> _get(String path, {Map<String, String>? query}) async {
|
||||||
|
final uri = Uri.parse('$_base$path').replace(queryParameters: query);
|
||||||
|
final res = await http.get(uri, headers: await _headers());
|
||||||
|
return jsonDecode(res.body);
|
||||||
|
}
|
||||||
|
|
||||||
|
Future<dynamic> _post(String path, Map<String, dynamic> body) async {
|
||||||
|
final res = await http.post(
|
||||||
|
Uri.parse('$_base$path'),
|
||||||
|
headers: await _headers(),
|
||||||
|
body: jsonEncode(body),
|
||||||
|
);
|
||||||
|
return jsonDecode(res.body);
|
||||||
|
}
|
||||||
|
|
||||||
|
Future<dynamic> _patch(String path, Map<String, dynamic> body) async {
|
||||||
|
final res = await http.patch(
|
||||||
|
Uri.parse('$_base$path'),
|
||||||
|
headers: await _headers(),
|
||||||
|
body: jsonEncode(body),
|
||||||
|
);
|
||||||
|
return jsonDecode(res.body);
|
||||||
|
}
|
||||||
|
|
||||||
|
ServiceEntity _fromApi(Map<String, dynamic> json) {
|
||||||
|
String range1Hour1 = json['range1_hour1']?.toString() ?? '0:0';
|
||||||
|
String range1Hour2 = json['range1_hour2']?.toString() ?? '0:0';
|
||||||
|
|
||||||
|
TimeOfDay parseTime(String s) {
|
||||||
|
final parts = s.split(':');
|
||||||
|
return TimeOfDay(
|
||||||
|
hour: int.tryParse(parts[0]) ?? 0,
|
||||||
|
minute: int.tryParse(parts.length > 1 ? parts[1] : '0') ?? 0,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
return ServiceEntity(
|
||||||
|
id: json['id']?.toString(),
|
||||||
|
professionalId: json['professional_id']?.toString() ?? '',
|
||||||
|
professionalScored: json['professional_scored'] as bool? ?? false,
|
||||||
|
userId: json['user_id']?.toString() ?? '',
|
||||||
|
userScored: json['user_scored'] as bool? ?? false,
|
||||||
|
address: json['address']?.toString() ?? '',
|
||||||
|
aditionalAddress: json['aditional_address']?.toString() ?? '',
|
||||||
|
latitude: double.tryParse(json['latitude']?.toString() ?? '0') ?? 0.0,
|
||||||
|
longitude: double.tryParse(json['longitude']?.toString() ?? '0') ?? 0.0,
|
||||||
|
day: json['day']?.toString() ?? '',
|
||||||
|
createdAt: json['created_at']?.toString() ?? DateTime.now().toIso8601String(),
|
||||||
|
description: json['description']?.toString() ?? '',
|
||||||
|
range1Hour1: parseTime(range1Hour1),
|
||||||
|
range1Hour2: parseTime(range1Hour2),
|
||||||
|
rate: json['rate']?.toString() ?? '',
|
||||||
|
status: intToEnumService((json['status'] as num?)?.toInt() ?? 0),
|
||||||
|
location: intToEnum((json['location'] as num?)?.toInt() ?? 0),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
Future<String> createService(ServiceEntity entity) async {
|
||||||
|
final data = await _post('/services', entity.toDocument());
|
||||||
|
return data['id']?.toString() ?? '';
|
||||||
|
}
|
||||||
|
|
||||||
|
Future<void> updateServiceStatus(String serviceId, ServiceStatus newStatus) async {
|
||||||
|
await _patch('/services/$serviceId', {'status': enumToIntService(newStatus)});
|
||||||
|
}
|
||||||
|
|
||||||
|
Stream<ServiceEntity> getService(String serviceId) {
|
||||||
|
final controller = StreamController<ServiceEntity>();
|
||||||
|
_get('/services', query: {'id': serviceId}).then((data) {
|
||||||
|
if (data is List && data.isNotEmpty) {
|
||||||
|
controller.add(_fromApi(data.first as Map<String, dynamic>));
|
||||||
|
} else if (data is Map) {
|
||||||
|
controller.add(_fromApi(data as Map<String, dynamic>));
|
||||||
|
}
|
||||||
|
controller.close();
|
||||||
|
}).catchError((e) {
|
||||||
|
controller.addError(e);
|
||||||
|
controller.close();
|
||||||
|
});
|
||||||
|
return controller.stream;
|
||||||
|
}
|
||||||
|
|
||||||
|
Stream<List<ServiceEntity>> getServicesForUser(String userId) {
|
||||||
|
return _streamList('/services', query: {'userId': userId}, statusFilter: [0, 1, 3]);
|
||||||
|
}
|
||||||
|
|
||||||
|
Stream<List<ServiceEntity>> getServicesForProfessional(String professionalId) {
|
||||||
|
return _streamList('/services', query: {'professionalId': professionalId}, statusFilter: [1, 3]);
|
||||||
|
}
|
||||||
|
|
||||||
|
Future<List<ServiceEntity>> getServicesForProfessionalforCalendar(String professionalId) async {
|
||||||
|
try {
|
||||||
|
final data = await _get('/services', query: {'professionalId': professionalId});
|
||||||
|
if (data is! List) return [];
|
||||||
|
return data
|
||||||
|
.map((e) => _fromApi(e as Map<String, dynamic>))
|
||||||
|
.where((s) => [0, 1, 2, 3, 6].contains(s.status.index))
|
||||||
|
.toList();
|
||||||
|
} catch (_) {
|
||||||
|
return [];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Stream<List<ServiceEntity>> getServicesHistoryForUser(String userId) {
|
||||||
|
return _streamList('/services', query: {'userId': userId}, statusFilter: [2, 4, 5]);
|
||||||
|
}
|
||||||
|
|
||||||
|
Stream<List<ServiceEntity>> getServicesHistoryForProfessional(String professionalId) {
|
||||||
|
return _streamList('/services', query: {'professionalId': professionalId}, statusFilter: [2, 4, 5]);
|
||||||
|
}
|
||||||
|
|
||||||
|
Stream<List<ServiceEntity>> getPendingServicesForProfessional(String professionalId) {
|
||||||
|
return _streamList('/services', query: {'professionalId': professionalId}, statusFilter: [0]);
|
||||||
|
}
|
||||||
|
|
||||||
|
Future<int> countPendingServicesForProfessional(String professionalId) async {
|
||||||
|
try {
|
||||||
|
final data = await _get('/services', query: {'professionalId': professionalId});
|
||||||
|
if (data is! List) return 0;
|
||||||
|
return data
|
||||||
|
.map((e) => _fromApi(e as Map<String, dynamic>))
|
||||||
|
.where((s) => s.status == ServiceStatus.pending)
|
||||||
|
.length;
|
||||||
|
} catch (_) {
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Future<void> setProfessionalScored(String serviceId) async {
|
||||||
|
await _patch('/services/$serviceId', {'professional_scored': true});
|
||||||
|
}
|
||||||
|
|
||||||
|
Future<void> setUserScored(String serviceId) async {
|
||||||
|
await _patch('/services/$serviceId', {'user_scored': true});
|
||||||
|
}
|
||||||
|
|
||||||
|
Stream<List<ServiceEntity>> _streamList(
|
||||||
|
String path, {
|
||||||
|
Map<String, String>? query,
|
||||||
|
List<int> statusFilter = const [],
|
||||||
|
}) {
|
||||||
|
final controller = StreamController<List<ServiceEntity>>();
|
||||||
|
_get(path, query: query).then((data) {
|
||||||
|
if (data is! List) {
|
||||||
|
controller.add([]);
|
||||||
|
} else {
|
||||||
|
var list = data.map((e) => _fromApi(e as Map<String, dynamic>)).toList();
|
||||||
|
if (statusFilter.isNotEmpty) {
|
||||||
|
list = list.where((s) => statusFilter.contains(s.status.index)).toList();
|
||||||
|
}
|
||||||
|
controller.add(list);
|
||||||
|
}
|
||||||
|
controller.close();
|
||||||
|
}).catchError((e) {
|
||||||
|
controller.add([]);
|
||||||
|
controller.close();
|
||||||
|
});
|
||||||
|
return controller.stream;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -11,8 +11,10 @@ dependencies:
|
|||||||
flutter:
|
flutter:
|
||||||
sdk: flutter
|
sdk: flutter
|
||||||
equatable: ^2.0.5
|
equatable: ^2.0.5
|
||||||
|
http: ^1.1.0
|
||||||
|
shared_preferences: ^2.0.10
|
||||||
|
|
||||||
# Firebase
|
# Firebase kept for FirebaseServiceRepository (legacy) and ServiceEntity uses Timestamp
|
||||||
cloud_firestore: ^4.15.4
|
cloud_firestore: ^4.15.4
|
||||||
firebase_core: ^2.25.4
|
firebase_core: ^2.25.4
|
||||||
|
|
||||||
|
|||||||
@@ -3,3 +3,4 @@ library setting_repository;
|
|||||||
export 'src/entities/entities.dart';
|
export 'src/entities/entities.dart';
|
||||||
export 'src/repositories/setting_repo.dart';
|
export 'src/repositories/setting_repo.dart';
|
||||||
export 'src/repositories/firebase_setting_repository.dart';
|
export 'src/repositories/firebase_setting_repository.dart';
|
||||||
|
export 'src/repositories/api_setting_repository.dart';
|
||||||
|
|||||||
@@ -0,0 +1,72 @@
|
|||||||
|
import 'dart:async';
|
||||||
|
import 'dart:convert';
|
||||||
|
import 'package:http/http.dart' as http;
|
||||||
|
import 'package:shared_preferences/shared_preferences.dart';
|
||||||
|
import 'package:setting_repository/src/entities/entities.dart';
|
||||||
|
import 'package:setting_repository/src/repositories/setting_repo.dart';
|
||||||
|
|
||||||
|
const _base = 'https://backend.prosapp.co/api/v1';
|
||||||
|
|
||||||
|
class ApiSettingRepository implements SettingRepository {
|
||||||
|
Future<String?> _getToken() async {
|
||||||
|
final prefs = await SharedPreferences.getInstance();
|
||||||
|
return prefs.getString('token');
|
||||||
|
}
|
||||||
|
|
||||||
|
Future<Map<String, String>> _headers() async {
|
||||||
|
final t = await _getToken();
|
||||||
|
return {
|
||||||
|
'Content-Type': 'application/json',
|
||||||
|
if (t != null) 'Authorization': 'Bearer $t',
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
Future<SettingEntity> getSettings() async {
|
||||||
|
try {
|
||||||
|
final res = await http.get(
|
||||||
|
Uri.parse('$_base/settings'),
|
||||||
|
headers: await _headers(),
|
||||||
|
);
|
||||||
|
final data = jsonDecode(res.body);
|
||||||
|
|
||||||
|
// Backend returns {key: string, value: any}[] or a flat map
|
||||||
|
Map<String, dynamic> doc = {};
|
||||||
|
if (data is List) {
|
||||||
|
for (final entry in data) {
|
||||||
|
if (entry is Map) {
|
||||||
|
final key = entry['key']?.toString();
|
||||||
|
final value = entry['value'];
|
||||||
|
if (key != null) doc[key] = value;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} else if (data is Map) {
|
||||||
|
doc = Map<String, dynamic>.from(data);
|
||||||
|
}
|
||||||
|
|
||||||
|
return SettingEntity.fromDocument(doc);
|
||||||
|
} catch (_) {
|
||||||
|
// Return safe defaults if settings call fails
|
||||||
|
return const SettingEntity(
|
||||||
|
tarifas: null,
|
||||||
|
domicilios: null,
|
||||||
|
google: null,
|
||||||
|
versionIos: null,
|
||||||
|
versionAndroid: null,
|
||||||
|
horaNotificacion: null,
|
||||||
|
tituloSoporte: null,
|
||||||
|
parrafoSoporte: null,
|
||||||
|
numeroSoporte: null,
|
||||||
|
emailSoporte: null,
|
||||||
|
diasSoporte: null,
|
||||||
|
horasSoporte: null,
|
||||||
|
politicasPrivacidad: null,
|
||||||
|
politicasPrivacidadTitle: null,
|
||||||
|
politicasPrivacidadBody: null,
|
||||||
|
terminosCondiciones: null,
|
||||||
|
terminosCondicionesTitle: null,
|
||||||
|
terminosCondicionesBody: null,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -11,8 +11,10 @@ dependencies:
|
|||||||
flutter:
|
flutter:
|
||||||
sdk: flutter
|
sdk: flutter
|
||||||
equatable: ^2.0.5
|
equatable: ^2.0.5
|
||||||
|
http: ^1.1.0
|
||||||
|
shared_preferences: ^2.0.10
|
||||||
|
|
||||||
# Firebase
|
# Firebase kept for FirebaseSettingRepository (legacy fallback)
|
||||||
cloud_firestore: ^4.15.4
|
cloud_firestore: ^4.15.4
|
||||||
firebase_core: ^2.25.4
|
firebase_core: ^2.25.4
|
||||||
|
|
||||||
|
|||||||
@@ -0,0 +1,271 @@
|
|||||||
|
import 'dart:async';
|
||||||
|
import 'dart:convert';
|
||||||
|
import 'package:http/http.dart' as http;
|
||||||
|
import 'package:shared_preferences/shared_preferences.dart';
|
||||||
|
import '../models/models.dart';
|
||||||
|
import 'user_repo.dart';
|
||||||
|
|
||||||
|
const _base = 'https://backend.prosapp.co/api/v1';
|
||||||
|
|
||||||
|
class ApiUserRepository implements UserRepository {
|
||||||
|
static ApiUserRepository? _instance;
|
||||||
|
|
||||||
|
// Cached user ID accessible without async for UI usage
|
||||||
|
static String? currentUserId;
|
||||||
|
|
||||||
|
final _controller = StreamController<MyUser?>.broadcast();
|
||||||
|
MyUser? _current;
|
||||||
|
String? _token;
|
||||||
|
|
||||||
|
Future<String?> _getToken() async {
|
||||||
|
if (_token != null) return _token;
|
||||||
|
final prefs = await SharedPreferences.getInstance();
|
||||||
|
return _token = prefs.getString('token');
|
||||||
|
}
|
||||||
|
|
||||||
|
Future<Map<String, String>> _headers() async {
|
||||||
|
final t = await _getToken();
|
||||||
|
return {
|
||||||
|
'Content-Type': 'application/json',
|
||||||
|
if (t != null) 'Authorization': 'Bearer $t',
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
Future<dynamic> _get(String path) async {
|
||||||
|
final res = await http.get(Uri.parse('$_base$path'), headers: await _headers());
|
||||||
|
return jsonDecode(res.body);
|
||||||
|
}
|
||||||
|
|
||||||
|
Future<dynamic> _post(String path, Map<String, dynamic> body, {bool auth = false}) async {
|
||||||
|
final h = await _headers();
|
||||||
|
if (!auth) h.remove('Authorization');
|
||||||
|
final res = await http.post(Uri.parse('$_base$path'), headers: h, body: jsonEncode(body));
|
||||||
|
return jsonDecode(res.body);
|
||||||
|
}
|
||||||
|
|
||||||
|
Future<dynamic> _patch(String path, Map<String, dynamic> body) async {
|
||||||
|
final res = await http.patch(Uri.parse('$_base$path'), headers: await _headers(), body: jsonEncode(body));
|
||||||
|
return jsonDecode(res.body);
|
||||||
|
}
|
||||||
|
|
||||||
|
MyUser _fromApi(Map<String, dynamic> json) {
|
||||||
|
return MyUser(
|
||||||
|
id: json['id']?.toString() ?? '',
|
||||||
|
email: json['email']?.toString(),
|
||||||
|
phone: json['phone']?.toString(),
|
||||||
|
name: json['name']?.toString(),
|
||||||
|
nickname: json['nickname']?.toString(),
|
||||||
|
city: json['city']?.toString(),
|
||||||
|
picture: json['picture']?.toString(),
|
||||||
|
birthday: json['birthday']?.toString(),
|
||||||
|
gender: json['gender']?.toString(),
|
||||||
|
proState: _proStateFromInt((json['pro_state'] as num?)?.toInt() ?? 0),
|
||||||
|
token: null,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
ProState _proStateFromInt(int v) {
|
||||||
|
switch (v) {
|
||||||
|
case 1:
|
||||||
|
return ProState.pending;
|
||||||
|
case 2:
|
||||||
|
return ProState.active;
|
||||||
|
case 3:
|
||||||
|
return ProState.denied;
|
||||||
|
default:
|
||||||
|
return ProState.inactive;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
Future<MyUser?> lastUser() async => _current;
|
||||||
|
|
||||||
|
@override
|
||||||
|
Stream<MyUser?> streamUser() => _controller.stream;
|
||||||
|
|
||||||
|
@override
|
||||||
|
Stream<bool> isAuthenticated() async* {
|
||||||
|
final t = await _getToken();
|
||||||
|
if (t != null) {
|
||||||
|
// Try to restore the current user from the API
|
||||||
|
try {
|
||||||
|
final data = await _get('/auth/me');
|
||||||
|
if (data != null) {
|
||||||
|
_current = _fromApi(data as Map<String, dynamic>);
|
||||||
|
currentUserId = _current?.id;
|
||||||
|
}
|
||||||
|
} catch (_) {}
|
||||||
|
}
|
||||||
|
yield t != null;
|
||||||
|
}
|
||||||
|
|
||||||
|
void _emit(MyUser? user) {
|
||||||
|
currentUserId = user?.id;
|
||||||
|
_controller.add(user);
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
Future<void> signIn(String email, String password) async {
|
||||||
|
final data = await _post('/auth/login', {'email': email, 'password': password});
|
||||||
|
_token = data['access_token'] as String?;
|
||||||
|
if (_token != null) {
|
||||||
|
final prefs = await SharedPreferences.getInstance();
|
||||||
|
await prefs.setString('token', _token!);
|
||||||
|
}
|
||||||
|
_current = _fromApi(data['user'] as Map<String, dynamic>);
|
||||||
|
_emit(_current);
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
Future<MyUser> signUp(MyUser myUser, String password) async {
|
||||||
|
final data = await _post('/auth/register', {
|
||||||
|
'email': myUser.email ?? '',
|
||||||
|
'password': password,
|
||||||
|
'name': myUser.name ?? myUser.email ?? '',
|
||||||
|
});
|
||||||
|
_token = data['access_token'] as String?;
|
||||||
|
if (_token != null) {
|
||||||
|
final prefs = await SharedPreferences.getInstance();
|
||||||
|
await prefs.setString('token', _token!);
|
||||||
|
}
|
||||||
|
_current = _fromApi(data['user'] as Map<String, dynamic>);
|
||||||
|
_emit(_current);
|
||||||
|
return _current!;
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
Future<void> logOut() async {
|
||||||
|
_token = null;
|
||||||
|
_current = null;
|
||||||
|
currentUserId = null;
|
||||||
|
final prefs = await SharedPreferences.getInstance();
|
||||||
|
await prefs.remove('token');
|
||||||
|
_controller.add(null);
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
Future<MyUser?> getMyUser(String myUserId) async {
|
||||||
|
try {
|
||||||
|
final data = await _get('/users/$myUserId');
|
||||||
|
return _fromApi(data as Map<String, dynamic>);
|
||||||
|
} catch (_) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
Future<void> updateUserInfo(MyUser myUser) async {
|
||||||
|
final body = <String, dynamic>{};
|
||||||
|
if (myUser.name != null) body['name'] = myUser.name;
|
||||||
|
if (myUser.city != null) body['city'] = myUser.city;
|
||||||
|
if (myUser.picture != null) body['picture'] = myUser.picture;
|
||||||
|
if (myUser.birthday != null) body['birthday'] = myUser.birthday;
|
||||||
|
if (myUser.gender != null) body['gender'] = myUser.gender;
|
||||||
|
if (myUser.phone != null) body['phone'] = myUser.phone;
|
||||||
|
if (body.isNotEmpty) {
|
||||||
|
await _patch('/users/me', body);
|
||||||
|
}
|
||||||
|
_current = _current?.copyWith(
|
||||||
|
name: myUser.name,
|
||||||
|
city: myUser.city,
|
||||||
|
picture: myUser.picture,
|
||||||
|
birthday: myUser.birthday,
|
||||||
|
gender: myUser.gender,
|
||||||
|
phone: myUser.phone,
|
||||||
|
);
|
||||||
|
_emit(_current);
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
Future<void> setUserData(MyUser user) => updateUserInfo(user);
|
||||||
|
|
||||||
|
@override
|
||||||
|
Future<void> createUser(MyUser myUser) async {
|
||||||
|
// Called after signUp; user already created in backend
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
Future<String> uploadPicture(String file, String userId) async {
|
||||||
|
final token = await _getToken();
|
||||||
|
final req = http.MultipartRequest('POST', Uri.parse('$_base/storage/upload'));
|
||||||
|
if (token != null) req.headers['Authorization'] = 'Bearer $token';
|
||||||
|
req.files.add(await http.MultipartFile.fromPath('file', file));
|
||||||
|
final streamed = await req.send();
|
||||||
|
final res = await http.Response.fromStream(streamed);
|
||||||
|
final body = jsonDecode(res.body) as Map<String, dynamic>;
|
||||||
|
final url = body['url'] as String;
|
||||||
|
await _patch('/users/me', {'picture': url});
|
||||||
|
return url;
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
Future<List<MyUser>> getUsersProfessionalActive() async {
|
||||||
|
try {
|
||||||
|
final data = await _get('/professionals') as List;
|
||||||
|
final List<MyUser> result = [];
|
||||||
|
for (final p in data) {
|
||||||
|
final userId = p['user_id']?.toString();
|
||||||
|
if (userId != null) {
|
||||||
|
final user = await getMyUser(userId);
|
||||||
|
if (user != null) result.add(user);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return result;
|
||||||
|
} catch (_) {
|
||||||
|
return [];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
Future<List<MyUser>> getUsersFromIds(Iterable<String> ids) async {
|
||||||
|
final result = <MyUser>[];
|
||||||
|
for (final id in ids) {
|
||||||
|
final u = await getMyUser(id);
|
||||||
|
if (u != null) result.add(u);
|
||||||
|
}
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
Future<String?> addEmailAndPassword(String email, String password) async {
|
||||||
|
try {
|
||||||
|
await _patch('/users/me', {'email': email});
|
||||||
|
return null;
|
||||||
|
} catch (e) {
|
||||||
|
return e.toString();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
Future<UpdatePassworErros?> updatePassword(String password, String newPassword) async {
|
||||||
|
// Backend doesn't expose a change-password endpoint with old password; stub
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
Future<void> signInWithPhoneNumber(String phoneNumber) async {
|
||||||
|
// The API's phone auth is direct — no OTP flow via this method
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
Future<bool> verifyOTP(String code) async => false;
|
||||||
|
|
||||||
|
@override
|
||||||
|
Future<void> addPhoneAuthCredential(
|
||||||
|
String password,
|
||||||
|
String phoneNumber, {
|
||||||
|
required Future<void> Function(Exception) verificationFailed,
|
||||||
|
required Future<void> Function(String) codeSent,
|
||||||
|
required Future<void> Function(String) codeAutoRetrievalTimeout,
|
||||||
|
}) async {
|
||||||
|
// Not supported by new REST API — stub
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
Future<bool> linkWithOTP(String phoneNumber, String verificationId, String code) async => false;
|
||||||
|
|
||||||
|
@override
|
||||||
|
Future<void> resetPassword(String email) async {
|
||||||
|
// Not supported by current API — stub
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -213,7 +213,7 @@ class FirebaseUserRepository implements UserRepository {
|
|||||||
|
|
||||||
@override
|
@override
|
||||||
addPhoneAuthCredential(String password, String phoneNumber,
|
addPhoneAuthCredential(String password, String phoneNumber,
|
||||||
{required Future<void> Function(FirebaseAuthException) verificationFailed,
|
{required Future<void> Function(Exception) verificationFailed,
|
||||||
required Future<void> Function(String) codeSent,
|
required Future<void> Function(String) codeSent,
|
||||||
required Future<void> Function(String) codeAutoRetrievalTimeout}) async {
|
required Future<void> Function(String) codeAutoRetrievalTimeout}) async {
|
||||||
await _firebaseAuth.verifyPhoneNumber(
|
await _firebaseAuth.verifyPhoneNumber(
|
||||||
|
|||||||
@@ -1,5 +1,3 @@
|
|||||||
import 'package:firebase_auth/firebase_auth.dart';
|
|
||||||
|
|
||||||
import '../../user_repository.dart';
|
import '../../user_repository.dart';
|
||||||
|
|
||||||
abstract class UserRepository {
|
abstract class UserRepository {
|
||||||
@@ -25,7 +23,7 @@ abstract class UserRepository {
|
|||||||
Future<bool> verifyOTP(String code);
|
Future<bool> verifyOTP(String code);
|
||||||
|
|
||||||
Future<void> addPhoneAuthCredential(String password, String phoneNumber,
|
Future<void> addPhoneAuthCredential(String password, String phoneNumber,
|
||||||
{required Future<void> Function(FirebaseAuthException) verificationFailed,
|
{required Future<void> Function(Exception) verificationFailed,
|
||||||
required Future<void> Function(String) codeSent,
|
required Future<void> Function(String) codeSent,
|
||||||
required Future<void> Function(String) codeAutoRetrievalTimeout});
|
required Future<void> Function(String) codeAutoRetrievalTimeout});
|
||||||
|
|
||||||
|
|||||||
@@ -1,43 +1,9 @@
|
|||||||
import 'dart:async';
|
import 'dart:async';
|
||||||
|
|
||||||
import 'package:firebase_auth/firebase_auth.dart';
|
|
||||||
|
|
||||||
class PhoneVerificationService {
|
class PhoneVerificationService {
|
||||||
final FirebaseAuth _firebaseAuth = FirebaseAuth.instance;
|
|
||||||
|
|
||||||
Stream<PhoneAuthEvent> verifyPhoneNumber(String phoneNumber) async* {
|
Stream<PhoneAuthEvent> verifyPhoneNumber(String phoneNumber) async* {
|
||||||
final StreamController<PhoneAuthEvent> phoneAuthController =
|
// ponytail: Firebase phone auth removed — stub until backend OTP is implemented
|
||||||
StreamController<PhoneAuthEvent>();
|
yield PhoneAuthEvent(PhoneAuthEventType.verificationFailed, Exception('Phone verification not supported'));
|
||||||
|
|
||||||
_firebaseAuth.verifyPhoneNumber(
|
|
||||||
phoneNumber: phoneNumber,
|
|
||||||
timeout: const Duration(seconds: 60),
|
|
||||||
verificationCompleted: (AuthCredential authCredential) async {
|
|
||||||
phoneAuthController
|
|
||||||
.add(PhoneAuthEvent.verificationCompleted(authCredential));
|
|
||||||
},
|
|
||||||
verificationFailed: (FirebaseAuthException authException) async {
|
|
||||||
phoneAuthController
|
|
||||||
.add(PhoneAuthEvent.verificationFailed(authException));
|
|
||||||
phoneAuthController.close();
|
|
||||||
},
|
|
||||||
codeAutoRetrievalTimeout: (String verificationId) async {
|
|
||||||
phoneAuthController
|
|
||||||
.add(PhoneAuthEvent.codeAutoRetrievalTimeout(verificationId));
|
|
||||||
},
|
|
||||||
codeSent: (String verificationId, int? resendToken) async {
|
|
||||||
phoneAuthController
|
|
||||||
.add(PhoneAuthEvent.codeSent(verificationId, resendToken));
|
|
||||||
},
|
|
||||||
);
|
|
||||||
|
|
||||||
await for (PhoneAuthEvent event in phoneAuthController.stream) {
|
|
||||||
yield event;
|
|
||||||
if (event.type == PhoneAuthEventType.verificationFailed) {
|
|
||||||
await phoneAuthController.close();
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -54,23 +20,16 @@ class PhoneAuthEvent {
|
|||||||
|
|
||||||
PhoneAuthEvent(this.type, this.data);
|
PhoneAuthEvent(this.type, this.data);
|
||||||
|
|
||||||
static PhoneAuthEvent verificationCompleted(AuthCredential authCredential) {
|
static PhoneAuthEvent verificationCompleted(dynamic credential) =>
|
||||||
return PhoneAuthEvent(
|
PhoneAuthEvent(PhoneAuthEventType.verificationCompleted, credential);
|
||||||
PhoneAuthEventType.verificationCompleted, authCredential);
|
|
||||||
}
|
|
||||||
|
|
||||||
static PhoneAuthEvent verificationFailed(
|
static PhoneAuthEvent verificationFailed(Exception e) =>
|
||||||
FirebaseAuthException authException) {
|
PhoneAuthEvent(PhoneAuthEventType.verificationFailed, e);
|
||||||
return PhoneAuthEvent(PhoneAuthEventType.verificationFailed, authException);
|
|
||||||
}
|
|
||||||
|
|
||||||
static PhoneAuthEvent codeAutoRetrievalTimeout(String verificationId) {
|
static PhoneAuthEvent codeAutoRetrievalTimeout(String verificationId) =>
|
||||||
return PhoneAuthEvent(
|
PhoneAuthEvent(PhoneAuthEventType.codeAutoRetrievalTimeout, verificationId);
|
||||||
PhoneAuthEventType.codeAutoRetrievalTimeout, verificationId);
|
|
||||||
}
|
|
||||||
|
|
||||||
static PhoneAuthEvent codeSent(String verificationId, int? resendToken) {
|
static PhoneAuthEvent codeSent(String verificationId, int? resendToken) =>
|
||||||
return PhoneAuthEvent(PhoneAuthEventType.codeSent,
|
PhoneAuthEvent(PhoneAuthEventType.codeSent,
|
||||||
{'verificationId': verificationId, 'resendToken': resendToken});
|
{'verificationId': verificationId, 'resendToken': resendToken});
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|||||||
@@ -5,3 +5,4 @@ export 'src/entities/entities.dart';
|
|||||||
export 'src/services/phone_verification_service.dart';
|
export 'src/services/phone_verification_service.dart';
|
||||||
export 'src/repositories/user_repo.dart';
|
export 'src/repositories/user_repo.dart';
|
||||||
export 'src/repositories/firebase_user_repository.dart';
|
export 'src/repositories/firebase_user_repository.dart';
|
||||||
|
export 'src/repositories/api_user_repository.dart';
|
||||||
|
|||||||
@@ -11,8 +11,10 @@ dependencies:
|
|||||||
flutter:
|
flutter:
|
||||||
sdk: flutter
|
sdk: flutter
|
||||||
equatable: ^2.0.5
|
equatable: ^2.0.5
|
||||||
|
http: ^1.1.0
|
||||||
|
shared_preferences: ^2.0.10
|
||||||
|
|
||||||
# Firebase
|
# Firebase kept for FirebaseUserRepository (legacy fallback)
|
||||||
firebase_auth: ^4.17.4
|
firebase_auth: ^4.17.4
|
||||||
cloud_firestore: ^4.15.4
|
cloud_firestore: ^4.15.4
|
||||||
firebase_storage: ^11.6.5
|
firebase_storage: ^11.6.5
|
||||||
|
|||||||
+3
-9
@@ -18,17 +18,12 @@ dependencies:
|
|||||||
cloud_firestore: ^4.15.4
|
cloud_firestore: ^4.15.4
|
||||||
community_material_icon: ^5.9.55
|
community_material_icon: ^5.9.55
|
||||||
cupertino_icons: ^1.0.2
|
cupertino_icons: ^1.0.2
|
||||||
diacritic: null
|
diacritic: ^0.1.5
|
||||||
equatable: ^2.0.5
|
equatable: ^2.0.5
|
||||||
file_picker: ^8.0.3
|
file_picker: ^8.0.3
|
||||||
firebase_auth: ^4.17.4
|
|
||||||
firebase_core: ^2.25.4
|
|
||||||
firebase_messaging: ^14.8.2
|
|
||||||
firebase_messaging_web: ^3.8.2
|
|
||||||
firebase_storage_web: ^3.9.7
|
|
||||||
flutter:
|
flutter:
|
||||||
sdk: flutter
|
sdk: flutter
|
||||||
flutter_animate: null
|
flutter_animate: ^4.5.0
|
||||||
flutter_bloc: ^8.1.4
|
flutter_bloc: ^8.1.4
|
||||||
flutter_dialogs: ^3.0.0
|
flutter_dialogs: ^3.0.0
|
||||||
flutter_email_sender: ^6.0.3
|
flutter_email_sender: ^6.0.3
|
||||||
@@ -42,7 +37,6 @@ dependencies:
|
|||||||
geocoding: ^3.0.0
|
geocoding: ^3.0.0
|
||||||
geolocator: ^12.0.0
|
geolocator: ^12.0.0
|
||||||
google_maps_flutter: ^2.5.0
|
google_maps_flutter: ^2.5.0
|
||||||
google_sign_in: ^6.1.4
|
|
||||||
http: ^1.1.0
|
http: ^1.1.0
|
||||||
image_picker: ^1.0.7
|
image_picker: ^1.0.7
|
||||||
injector: ^3.0.0
|
injector: ^3.0.0
|
||||||
@@ -65,7 +59,7 @@ dependencies:
|
|||||||
path: packages/setting_repository
|
path: packages/setting_repository
|
||||||
shared_preferences: ^2.0.10
|
shared_preferences: ^2.0.10
|
||||||
shimmer: ^3.0.0
|
shimmer: ^3.0.0
|
||||||
table_calendar: null
|
table_calendar: ^3.1.1
|
||||||
universal_html: ^2.2.3
|
universal_html: ^2.2.3
|
||||||
url_launcher: ^6.1.10
|
url_launcher: ^6.1.10
|
||||||
user_repository:
|
user_repository:
|
||||||
|
|||||||
Reference in New Issue
Block a user