diff --git a/lib/blocs/service_bloc/service_bloc.dart b/lib/blocs/service_bloc/service_bloc.dart index 05660e3..3ba91e0 100644 --- a/lib/blocs/service_bloc/service_bloc.dart +++ b/lib/blocs/service_bloc/service_bloc.dart @@ -33,7 +33,10 @@ class ServiceBloc extends Bloc { on(_onLoadServicesHistoryForUser); on( _onLoadServicesHistoryForProfessional); + on( + _onLoadPendingServicesForProfessional); on(_onUpdateProfessionalScored); + on(_onUpdateUserScored); } void _onCreateService(CreateService event, Emitter emit) async { @@ -224,6 +227,40 @@ class ServiceBloc extends Bloc { } } + void _onLoadPendingServicesForProfessional( + LoadPendingServicesForProfessional event, + Emitter emit) async { + try { + final servicesStream = + _serviceRepository.getPendingServicesForProfessional(event.userId); + + await for (var services in servicesStream) { + if (services.isEmpty) { + emit(const ServicesForUserLoaded([])); + return; + } + final userIds = services.map((e) => e.userId); + + final List users = + await _userRepository.getUsersFromIds(userIds); + + final usersDir = {for (var e in users) e.id: e}; + + final servicesInfo = services.map((e) { + return ServiceInfoUI( + service: e, + user: usersDir[e.userId]!, + ); + }).toList(); + + emit(ServicesForUserLoaded(servicesInfo)); + } + } catch (e) { + log(e.toString()); + emit(CreateServiceFailure()); + } + } + void _onUpdateServiceStatus( UpdateServiceStatus event, Emitter emit) async { try { @@ -242,7 +279,19 @@ class ServiceBloc extends Bloc { try { emit(CreateServiceLoading()); await _serviceRepository.setProfessionalScored(event.serviceId); - emit(ProfessionalScoredUpdated()); + emit(ScoredUpdated()); + } catch (e) { + log(e.toString()); + emit(CreateServiceFailure()); + } + } + + void _onUpdateUserScored( + UpdateUserScored event, Emitter emit) async { + try { + emit(CreateServiceLoading()); + await _serviceRepository.setUserScored(event.serviceId); + emit(ScoredUpdated()); } catch (e) { log(e.toString()); emit(CreateServiceFailure()); diff --git a/lib/blocs/service_bloc/service_event.dart b/lib/blocs/service_bloc/service_event.dart index 5efe9e8..e992fee 100644 --- a/lib/blocs/service_bloc/service_event.dart +++ b/lib/blocs/service_bloc/service_event.dart @@ -35,6 +35,15 @@ class LoadServicesForUser extends ServiceEvent { List get props => [userId]; } +class LoadPendingServicesForProfessional extends ServiceEvent { + final String userId; + + const LoadPendingServicesForProfessional(this.userId); + + @override + List get props => [userId]; +} + class LoadServicesForProfessional extends ServiceEvent { final String userId; @@ -80,6 +89,15 @@ class UpdateProfessionalScored extends ServiceEvent { List get props => [serviceId]; } +class UpdateUserScored extends ServiceEvent { + final String serviceId; + + const UpdateUserScored(this.serviceId); + + @override + List get props => [serviceId]; +} + class CreateService extends ServiceEvent { final String professionalId; final bool? professionalScored; diff --git a/lib/blocs/service_bloc/service_state.dart b/lib/blocs/service_bloc/service_state.dart index 1e8cd71..c4c3777 100644 --- a/lib/blocs/service_bloc/service_state.dart +++ b/lib/blocs/service_bloc/service_state.dart @@ -49,4 +49,4 @@ class ServiceStatusUpdated extends ServiceState { List get props => [newStatus]; } -class ProfessionalScoredUpdated extends ServiceState {} +class ScoredUpdated extends ServiceState {} diff --git a/lib/components/general_drawer.dart b/lib/components/general_drawer.dart index 12b6103..d95ffc3 100644 --- a/lib/components/general_drawer.dart +++ b/lib/components/general_drawer.dart @@ -229,13 +229,8 @@ class GeneralDrawer extends StatelessWidget { return ListTile( onTap: () {}, - trailing: const Icon(Icons.keyboard_arrow_right, - color: Colors.black), - title: const Text( - 'Reputación', - style: TextStyle(color: Colors.black), - ), - subtitle: Row( + trailing: const Icon(Icons.keyboard_arrow_right, color: Colors.black), + title: Row( children: [ RatingBar.builder( initialRating: calculoRating(average), @@ -260,6 +255,10 @@ class GeneralDrawer extends StatelessWidget { ), ); }), + const Divider( + height: 1, + thickness: 0.5, + ), Padding( padding: const EdgeInsets.only(top: 10), child: Text( diff --git a/lib/screens/home/home_screen.dart b/lib/screens/home/home_screen.dart index 94a376e..eef1ceb 100644 --- a/lib/screens/home/home_screen.dart +++ b/lib/screens/home/home_screen.dart @@ -2,6 +2,7 @@ import 'package:flutter/material.dart'; import 'package:flutter_bloc/flutter_bloc.dart'; import 'package:prosappco/blocs/professional_bloc/professional_bloc.dart'; import 'package:prosappco/components/general_drawer.dart'; +import 'package:prosappco/screens/lists/professional_pending_service_list.dart'; import 'package:prosappco/screens/user/user_map_screen.dart'; class HomeScreen extends StatelessWidget { @@ -16,10 +17,10 @@ class HomeScreen extends StatelessWidget { ? Scaffold( backgroundColor: Theme.of(context).colorScheme.background, drawer: GeneralDrawer(), - appBar: AppBar(), - body: const Center( - child: Text('Bienvenido profesional'), + appBar: AppBar( + title: const Text('Solicitudes'), ), + body: ProfessionalPendingServiceListScreen(), ) : Scaffold( backgroundColor: Theme.of(context).colorScheme.background, diff --git a/lib/screens/lists/professional_list_screen.dart b/lib/screens/lists/professional_list_screen.dart index 40cf5ae..5c4cbe6 100644 --- a/lib/screens/lists/professional_list_screen.dart +++ b/lib/screens/lists/professional_list_screen.dart @@ -1,3 +1,4 @@ +import 'package:firebase_auth/firebase_auth.dart'; import 'package:flutter/cupertino.dart'; import 'package:flutter/material.dart'; import 'package:flutter_bloc/flutter_bloc.dart'; @@ -71,17 +72,21 @@ class _ProfessionalListScreenState extends State { _body(ProfessionalListState state) { if (state is ProfessionalListSuccess) { final List filteredUsers; - // filtrar por nombre - if (_searchController.text.isNotEmpty) { - filteredUsers = state.users - .where((element) => removeDiacritics(element.myUser.name!) - .toLowerCase() - .contains( - removeDiacritics(_searchController.text.toLowerCase()))) - .toList(); - } else { - filteredUsers = state.users; - } + + // TODO : encaso de filtro dañado + // if (_searchController.text.isNotEmpty) { + filteredUsers = state.users + .where((element) => removeDiacritics(element.myUser.name!) + .toLowerCase() + .contains(removeDiacritics(_searchController.text.toLowerCase()))) + .where((user) => + user.myUser.id != FirebaseAuth.instance.currentUser!.uid) + .toList(); + // } else { + // filteredUsers = state.users + // // .where((user) => user.myUser.id != FirebaseAuth.instance.currentUser!.uid) + // .toList(); + // } if (filteredUsers.isEmpty) { return const Center( diff --git a/lib/screens/lists/professional_pending_service_list.dart b/lib/screens/lists/professional_pending_service_list.dart new file mode 100644 index 0000000..88e3bf2 --- /dev/null +++ b/lib/screens/lists/professional_pending_service_list.dart @@ -0,0 +1,216 @@ +import 'package:firebase_auth/firebase_auth.dart'; +import 'package:flutter/cupertino.dart'; +import 'package:flutter/material.dart'; +import 'package:flutter_bloc/flutter_bloc.dart'; +import 'package:injector/injector.dart'; +import 'package:intl/intl.dart'; +import 'package:professional_repository/professional_repository.dart'; +import 'package:prosappco/blocs/service_bloc/service_bloc.dart'; +import 'package:prosappco/screens/service/professional_service_screen.dart'; +import 'package:service_repository/service_repository.dart'; +import 'package:shimmer/shimmer.dart'; + +class ProfessionalPendingServiceListScreen extends StatefulWidget { + const ProfessionalPendingServiceListScreen({super.key}); + + @override + State createState() => + _ProfessionalPendingServiceListScreenState(); +} + +class _ProfessionalPendingServiceListScreenState + extends State { + late final ServiceBloc serviceBloc; + + @override + void initState() { + super.initState(); + + serviceBloc = Injector.appInstance.get(); + + serviceBloc.add(LoadPendingServicesForProfessional( + FirebaseAuth.instance.currentUser!.uid)); + } + + @override + Widget build(BuildContext context) { + return BlocProvider( + create: (context) => serviceBloc, + child: Scaffold( + body: BlocBuilder( + builder: (context, serviceState) { + if (serviceState is ServicesForUserLoaded) { + return serviceState.services.isEmpty + ? const Center( + child: Text('No tienes solicitudes'), + ) + : ListView.builder( + itemCount: serviceState.services.length, + itemBuilder: (_, index) { + final serviceInfo = serviceState.services[index]; + final user = serviceInfo.user; + final service = serviceInfo.service; + + return Container( + decoration: BoxDecoration( + border: Border( + bottom: BorderSide( + color: Colors.grey.withOpacity(0.2)), + ), + ), + child: ListTile( + onTap: () { + Navigator.push( + context, + CupertinoPageRoute( + builder: (context) => + ProfessionalServiceScreen( + serviceId: service.id!, + ), + ), + ); + }, + leading: Container( + width: 60, + height: 60, + decoration: BoxDecoration( + color: Colors.grey.shade300, + shape: BoxShape.circle, + image: user.picture == null + ? null + : DecorationImage( + image: NetworkImage(user.picture!), + fit: BoxFit.contain, + ), + ), + child: user.picture == null + ? Icon( + CupertinoIcons.person, + color: Colors.grey.shade400, + size: 40, + ) + : null, + ), + title: Row( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Flexible( + child: Text( + '${user.name}', + overflow: TextOverflow.ellipsis, + style: const TextStyle( + fontWeight: FontWeight.bold, + ), + ), + ), + const SizedBox(width: 5), + Text( + '${DateFormat('dd MMMM', 'es').format(DateTime.parse(service.day))} - ${ScheduleEntity.getFormatTime(service.range1Hour1)}', + style: TextStyle( + color: Colors.grey[600], + ), + ), + ], + ), + subtitle: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Row( + children: [ + const Text( + 'Estado:', + style: TextStyle( + fontStyle: FontStyle.italic, + ), + ), + const SizedBox(width: 5), + customStatus(service), + ], + ), + service.description.isEmpty + ? Container() + : Text( + '"${service.description.trim()}"', + overflow: TextOverflow.ellipsis, + ), + ], + ), + ), + ); + }, + ); + } + + return Shimmer.fromColors( + baseColor: Colors.grey[300]!, + highlightColor: Colors.grey[100]!, + child: ListView.builder( + itemCount: 10, + itemBuilder: (_, __) => ListTile( + leading: CircleAvatar( + backgroundColor: Colors.grey[300], + radius: 30, + ), + title: Container( + height: 20, + decoration: BoxDecoration( + color: Colors.grey.shade300, + borderRadius: BorderRadius.circular(8), + ), + ), + subtitle: Container( + height: 15, + decoration: BoxDecoration( + color: Colors.grey.shade300, + borderRadius: BorderRadius.circular(8), + ), + ), + ), + ), + ); + }, + ), + ), + ); + } + + Container customStatus(ServiceEntity service) { + if (service.status == ServiceStatus.pending) { + return itemStatus(Colors.black54, 'Pendiente'); + } + if (service.status == ServiceStatus.acepted) { + return itemStatus(Colors.green, 'Aceptado'); + } + if (service.status == ServiceStatus.active) { + return itemStatus(Colors.blueAccent, 'Activo'); + } + + return itemStatus(Colors.red, 'Cancelado'); + } + + Container itemStatus(Color color, String text) { + return Container( + padding: const EdgeInsets.symmetric( + horizontal: 8, + vertical: 3, + ), + decoration: BoxDecoration( + color: color, + borderRadius: BorderRadius.circular(8), + ), + child: Text( + text, + style: const TextStyle( + fontStyle: FontStyle.italic, + color: Colors.white, + ), + ), + ); + } +} + +// pending - 0 +// acepted - 1 +// active - 2 +// cancelled - 3 +// completed - 4 \ No newline at end of file diff --git a/lib/screens/lists/professional_service_history_list_screen.dart b/lib/screens/lists/professional_service_history_list_screen.dart index c1264c8..4bf56f8 100644 --- a/lib/screens/lists/professional_service_history_list_screen.dart +++ b/lib/screens/lists/professional_service_history_list_screen.dart @@ -7,7 +7,6 @@ import 'package:intl/intl.dart'; import 'package:professional_repository/professional_repository.dart'; import 'package:prosappco/blocs/service_bloc/service_bloc.dart'; import 'package:prosappco/screens/service/professional_service_screen.dart'; -import 'package:prosappco/screens/service/service_screen.dart'; import 'package:service_repository/service_repository.dart'; import 'package:shimmer/shimmer.dart'; diff --git a/lib/screens/lists/professional_service_list_screen.dart b/lib/screens/lists/professional_service_list_screen.dart index 64f9784..b418fa3 100644 --- a/lib/screens/lists/professional_service_list_screen.dart +++ b/lib/screens/lists/professional_service_list_screen.dart @@ -7,7 +7,6 @@ import 'package:intl/intl.dart'; import 'package:professional_repository/professional_repository.dart'; import 'package:prosappco/blocs/service_bloc/service_bloc.dart'; import 'package:prosappco/screens/service/professional_service_screen.dart'; -import 'package:prosappco/screens/service/service_screen.dart'; import 'package:service_repository/service_repository.dart'; import 'package:shimmer/shimmer.dart'; diff --git a/lib/screens/lists/user_service_history_list_screen.dart b/lib/screens/lists/user_service_history_list_screen.dart index 451bef2..37a90e1 100644 --- a/lib/screens/lists/user_service_history_list_screen.dart +++ b/lib/screens/lists/user_service_history_list_screen.dart @@ -6,7 +6,6 @@ import 'package:injector/injector.dart'; import 'package:intl/intl.dart'; import 'package:professional_repository/professional_repository.dart'; import 'package:prosappco/blocs/service_bloc/service_bloc.dart'; -import 'package:prosappco/screens/service/service_screen.dart'; import 'package:prosappco/screens/service/user_service_screen.dart'; import 'package:service_repository/service_repository.dart'; import 'package:shimmer/shimmer.dart'; diff --git a/lib/screens/lists/user_service_list_screen.dart b/lib/screens/lists/user_service_list_screen.dart index 0956aab..fc30ba1 100644 --- a/lib/screens/lists/user_service_list_screen.dart +++ b/lib/screens/lists/user_service_list_screen.dart @@ -6,7 +6,6 @@ import 'package:injector/injector.dart'; import 'package:intl/intl.dart'; import 'package:professional_repository/professional_repository.dart'; import 'package:prosappco/blocs/service_bloc/service_bloc.dart'; -import 'package:prosappco/screens/service/service_screen.dart'; import 'package:prosappco/screens/service/user_service_screen.dart'; import 'package:service_repository/service_repository.dart'; import 'package:shimmer/shimmer.dart'; diff --git a/lib/screens/score/score_screen.dart b/lib/screens/score/score_screen.dart index 67206b8..c4f3ad9 100644 --- a/lib/screens/score/score_screen.dart +++ b/lib/screens/score/score_screen.dart @@ -319,9 +319,16 @@ class _ScoreScreenState extends State { BlocProvider.of(context) .add(SendScoreEvent(comment: comment)); - context - .read() - .add(UpdateProfessionalScored(widget.service.id!)); + if (widget.service.userId == + FirebaseAuth.instance.currentUser!.uid) { + context + .read() + .add(UpdateProfessionalScored(widget.service.id!)); + } else { + context + .read() + .add(UpdateUserScored(widget.service.id!)); + } Navigator.pop(context); }, diff --git a/lib/screens/user/user_calendar_screen.dart b/lib/screens/user/user_calendar_screen.dart index 8aa6124..d890538 100644 --- a/lib/screens/user/user_calendar_screen.dart +++ b/lib/screens/user/user_calendar_screen.dart @@ -20,12 +20,16 @@ class UserCalendarScreen extends StatefulWidget { class UserCalendarScreenState extends State { final settingRepository = Injector.appInstance.get(); + final serviceRepository = + Injector.appInstance.get(); SettingEntity? settings; DateTime today = DateTime.now(); DateTime now = DateTime.now(); late int numDay; + List? _services; + CalendarFormat _calendarFormat = CalendarFormat.month; @override @@ -36,6 +40,7 @@ class UserCalendarScreenState extends State { numDay = today.weekday; _loadSettings(); + _loadServices(); } void _loadSettings() { @@ -46,6 +51,17 @@ class UserCalendarScreenState extends State { ); } + void _loadServices() { + serviceRepository + .getServicesForProfessionalforCalendar( + widget.userProfessional.myUser.id) + .then((services) { + setState(() { + _services = services; + }); + }); + } + void _onDaySelected(DateTime day, DateTime focusedDay) { setState(() { today = day; @@ -164,7 +180,7 @@ class UserCalendarScreenState extends State { schedule.range2Hour2!, ); - return rangesItemList(ranges, null); + return rangesItemList(ranges, _services); } List ranges1 = TimeOfDayUtils.genRanges( @@ -177,23 +193,15 @@ class UserCalendarScreenState extends State { ); return [ - ...rangesItemList(ranges1, null), - ...rangesItemList(ranges2, null), + ...rangesItemList(ranges1, _services), + ...rangesItemList(ranges2, _services), ]; } bool _isHora1Ocupada(TimeOfDay hora1, List? events) { if (events != null) { for (ServiceEntity event in events) { - DateTime time1 = DateTime( - today.year, - today.month, - today.day, - hora1.hour, - hora1.minute, - ); - - if (event.range1Hour1 == time1.toString()) { + if (hora1 == event.range1Hour1) { return true; } } @@ -238,7 +246,7 @@ class UserCalendarScreenState extends State { ), ), title: Text( - time.format(context), + ScheduleEntity.getFormatTime(time) ?? '', style: const TextStyle(fontSize: 15, fontWeight: FontWeight.bold), ), subtitle: const Text( diff --git a/lib/screens/user/user_map_screen.dart b/lib/screens/user/user_map_screen.dart index 5351c18..fad4941 100644 --- a/lib/screens/user/user_map_screen.dart +++ b/lib/screens/user/user_map_screen.dart @@ -16,10 +16,8 @@ import 'package:prosappco/blocs/my_user_bloc/my_user_bloc.dart'; import 'package:prosappco/blocs/professional_list_bloc/professional_list_bloc.dart'; import 'package:prosappco/blocs/service_bloc/service_bloc.dart'; import 'package:prosappco/constansts.dart'; -import 'package:prosappco/screens/lists/city_list_screen.dart'; import 'package:prosappco/screens/lists/professional_list_screen.dart'; import 'package:prosappco/screens/user/user_service_screen.dart'; -import 'package:prosappco/screens/web/web_view_screen.dart'; import 'package:prosappco/utils/time_of_day_extension.dart'; import 'package:service_repository/service_repository.dart'; import 'package:setting_repository/setting_repository.dart'; diff --git a/packages/service_repository/lib/src/repositories/firebase_service_repository.dart b/packages/service_repository/lib/src/repositories/firebase_service_repository.dart index 21da88d..80f9a40 100644 --- a/packages/service_repository/lib/src/repositories/firebase_service_repository.dart +++ b/packages/service_repository/lib/src/repositories/firebase_service_repository.dart @@ -48,6 +48,17 @@ class FirebaseServiceRepository { .toList()); } + Future> getServicesForProfessionalforCalendar( + String professionalId) async { + QuerySnapshot> query = await serviceCollection + .where('professional_id', isEqualTo: professionalId) + .where('status', whereIn: [0, 1, 2, 3]).get(); + + return query.docs + .map((doc) => ServiceEntity.fromDocument(doc.data(), doc.id)) + .toList(); + } + Stream> getServicesHistoryForUser(String userId) { return serviceCollection .where('user_id', isEqualTo: userId) @@ -69,6 +80,17 @@ class FirebaseServiceRepository { .toList()); } + Stream> getPendingServicesForProfessional( + String professionalId) { + return serviceCollection + .where('professional_id', isEqualTo: professionalId) + .where('status', whereIn: [0]) + .snapshots() + .map((querySnapshot) => querySnapshot.docs + .map((doc) => ServiceEntity.fromDocument(doc.data(), doc.id)) + .toList()); + } + // actualizar professionalScored a true Future setProfessionalScored(String serviceId) { @@ -80,17 +102,6 @@ class FirebaseServiceRepository { Future setUserScored(String serviceId) { return serviceCollection.doc(serviceId).update({'user_scored': true}); } - - // TODO: NEW - Stream> getServicesByDay(String day) { - return serviceCollection - .where('day', isEqualTo: day) - .where('status', whereIn: [0, 1, 2, 3]) - .snapshots() - .map((querySnapshot) => querySnapshot.docs - .map((doc) => ServiceEntity.fromDocument(doc.data(), doc.id)) - .toList()); - } }