This commit is contained in:
Felipe
2024-04-18 15:51:23 -05:00
parent 631b8c5801
commit 8084d5fc7f
15 changed files with 364 additions and 56 deletions
+50 -1
View File
@@ -33,7 +33,10 @@ class ServiceBloc extends Bloc<ServiceEvent, ServiceState> {
on<LoadServicesHistoryForUser>(_onLoadServicesHistoryForUser); on<LoadServicesHistoryForUser>(_onLoadServicesHistoryForUser);
on<LoadServicesHistoryForProfessional>( on<LoadServicesHistoryForProfessional>(
_onLoadServicesHistoryForProfessional); _onLoadServicesHistoryForProfessional);
on<LoadPendingServicesForProfessional>(
_onLoadPendingServicesForProfessional);
on<UpdateProfessionalScored>(_onUpdateProfessionalScored); on<UpdateProfessionalScored>(_onUpdateProfessionalScored);
on<UpdateUserScored>(_onUpdateUserScored);
} }
void _onCreateService(CreateService event, Emitter<ServiceState> emit) async { void _onCreateService(CreateService event, Emitter<ServiceState> emit) async {
@@ -224,6 +227,40 @@ class ServiceBloc extends Bloc<ServiceEvent, ServiceState> {
} }
} }
void _onLoadPendingServicesForProfessional(
LoadPendingServicesForProfessional event,
Emitter<ServiceState> 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<MyUser> 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( void _onUpdateServiceStatus(
UpdateServiceStatus event, Emitter<ServiceState> emit) async { UpdateServiceStatus event, Emitter<ServiceState> emit) async {
try { try {
@@ -242,7 +279,19 @@ class ServiceBloc extends Bloc<ServiceEvent, ServiceState> {
try { try {
emit(CreateServiceLoading()); emit(CreateServiceLoading());
await _serviceRepository.setProfessionalScored(event.serviceId); await _serviceRepository.setProfessionalScored(event.serviceId);
emit(ProfessionalScoredUpdated()); emit(ScoredUpdated());
} catch (e) {
log(e.toString());
emit(CreateServiceFailure());
}
}
void _onUpdateUserScored(
UpdateUserScored event, Emitter<ServiceState> emit) async {
try {
emit(CreateServiceLoading());
await _serviceRepository.setUserScored(event.serviceId);
emit(ScoredUpdated());
} catch (e) { } catch (e) {
log(e.toString()); log(e.toString());
emit(CreateServiceFailure()); emit(CreateServiceFailure());
+18
View File
@@ -35,6 +35,15 @@ class LoadServicesForUser extends ServiceEvent {
List<Object> get props => [userId]; List<Object> get props => [userId];
} }
class LoadPendingServicesForProfessional extends ServiceEvent {
final String userId;
const LoadPendingServicesForProfessional(this.userId);
@override
List<Object> get props => [userId];
}
class LoadServicesForProfessional extends ServiceEvent { class LoadServicesForProfessional extends ServiceEvent {
final String userId; final String userId;
@@ -80,6 +89,15 @@ class UpdateProfessionalScored extends ServiceEvent {
List<Object> get props => [serviceId]; List<Object> get props => [serviceId];
} }
class UpdateUserScored extends ServiceEvent {
final String serviceId;
const UpdateUserScored(this.serviceId);
@override
List<Object> get props => [serviceId];
}
class CreateService extends ServiceEvent { class CreateService extends ServiceEvent {
final String professionalId; final String professionalId;
final bool? professionalScored; final bool? professionalScored;
+1 -1
View File
@@ -49,4 +49,4 @@ class ServiceStatusUpdated extends ServiceState {
List<Object> get props => [newStatus]; List<Object> get props => [newStatus];
} }
class ProfessionalScoredUpdated extends ServiceState {} class ScoredUpdated extends ServiceState {}
+6 -7
View File
@@ -229,13 +229,8 @@ class GeneralDrawer extends StatelessWidget {
return ListTile( return ListTile(
onTap: () {}, onTap: () {},
trailing: const Icon(Icons.keyboard_arrow_right, trailing: const Icon(Icons.keyboard_arrow_right, color: Colors.black),
color: Colors.black), title: Row(
title: const Text(
'Reputación',
style: TextStyle(color: Colors.black),
),
subtitle: Row(
children: [ children: [
RatingBar.builder( RatingBar.builder(
initialRating: calculoRating(average), initialRating: calculoRating(average),
@@ -260,6 +255,10 @@ class GeneralDrawer extends StatelessWidget {
), ),
); );
}), }),
const Divider(
height: 1,
thickness: 0.5,
),
Padding( Padding(
padding: const EdgeInsets.only(top: 10), padding: const EdgeInsets.only(top: 10),
child: Text( child: Text(
+4 -3
View File
@@ -2,6 +2,7 @@ import 'package:flutter/material.dart';
import 'package:flutter_bloc/flutter_bloc.dart'; import 'package:flutter_bloc/flutter_bloc.dart';
import 'package:prosappco/blocs/professional_bloc/professional_bloc.dart'; import 'package:prosappco/blocs/professional_bloc/professional_bloc.dart';
import 'package:prosappco/components/general_drawer.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'; import 'package:prosappco/screens/user/user_map_screen.dart';
class HomeScreen extends StatelessWidget { class HomeScreen extends StatelessWidget {
@@ -16,10 +17,10 @@ class HomeScreen extends StatelessWidget {
? Scaffold( ? Scaffold(
backgroundColor: Theme.of(context).colorScheme.background, backgroundColor: Theme.of(context).colorScheme.background,
drawer: GeneralDrawer(), drawer: GeneralDrawer(),
appBar: AppBar(), appBar: AppBar(
body: const Center( title: const Text('Solicitudes'),
child: Text('Bienvenido profesional'),
), ),
body: ProfessionalPendingServiceListScreen(),
) )
: Scaffold( : Scaffold(
backgroundColor: Theme.of(context).colorScheme.background, backgroundColor: Theme.of(context).colorScheme.background,
+16 -11
View File
@@ -1,3 +1,4 @@
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';
@@ -71,17 +72,21 @@ class _ProfessionalListScreenState extends State<ProfessionalListScreen> {
_body(ProfessionalListState state) { _body(ProfessionalListState state) {
if (state is ProfessionalListSuccess) { if (state is ProfessionalListSuccess) {
final List<UserProfessional> filteredUsers; final List<UserProfessional> filteredUsers;
// filtrar por nombre
if (_searchController.text.isNotEmpty) { // TODO : encaso de filtro dañado
filteredUsers = state.users // if (_searchController.text.isNotEmpty) {
.where((element) => removeDiacritics(element.myUser.name!) filteredUsers = state.users
.toLowerCase() .where((element) => removeDiacritics(element.myUser.name!)
.contains( .toLowerCase()
removeDiacritics(_searchController.text.toLowerCase()))) .contains(removeDiacritics(_searchController.text.toLowerCase())))
.toList(); .where((user) =>
} else { user.myUser.id != FirebaseAuth.instance.currentUser!.uid)
filteredUsers = state.users; .toList();
} // } else {
// filteredUsers = state.users
// // .where((user) => user.myUser.id != FirebaseAuth.instance.currentUser!.uid)
// .toList();
// }
if (filteredUsers.isEmpty) { if (filteredUsers.isEmpty) {
return const Center( return const Center(
@@ -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<ProfessionalPendingServiceListScreen> createState() =>
_ProfessionalPendingServiceListScreenState();
}
class _ProfessionalPendingServiceListScreenState
extends State<ProfessionalPendingServiceListScreen> {
late final ServiceBloc serviceBloc;
@override
void initState() {
super.initState();
serviceBloc = Injector.appInstance.get<ServiceBloc>();
serviceBloc.add(LoadPendingServicesForProfessional(
FirebaseAuth.instance.currentUser!.uid));
}
@override
Widget build(BuildContext context) {
return BlocProvider<ServiceBloc>(
create: (context) => serviceBloc,
child: Scaffold(
body: BlocBuilder<ServiceBloc, ServiceState>(
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
@@ -7,7 +7,6 @@ import 'package:intl/intl.dart';
import 'package:professional_repository/professional_repository.dart'; import 'package:professional_repository/professional_repository.dart';
import 'package:prosappco/blocs/service_bloc/service_bloc.dart'; import 'package:prosappco/blocs/service_bloc/service_bloc.dart';
import 'package:prosappco/screens/service/professional_service_screen.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:service_repository/service_repository.dart';
import 'package:shimmer/shimmer.dart'; import 'package:shimmer/shimmer.dart';
@@ -7,7 +7,6 @@ import 'package:intl/intl.dart';
import 'package:professional_repository/professional_repository.dart'; import 'package:professional_repository/professional_repository.dart';
import 'package:prosappco/blocs/service_bloc/service_bloc.dart'; import 'package:prosappco/blocs/service_bloc/service_bloc.dart';
import 'package:prosappco/screens/service/professional_service_screen.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:service_repository/service_repository.dart';
import 'package:shimmer/shimmer.dart'; import 'package:shimmer/shimmer.dart';
@@ -6,7 +6,6 @@ import 'package:injector/injector.dart';
import 'package:intl/intl.dart'; import 'package:intl/intl.dart';
import 'package:professional_repository/professional_repository.dart'; import 'package:professional_repository/professional_repository.dart';
import 'package:prosappco/blocs/service_bloc/service_bloc.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:prosappco/screens/service/user_service_screen.dart';
import 'package:service_repository/service_repository.dart'; import 'package:service_repository/service_repository.dart';
import 'package:shimmer/shimmer.dart'; import 'package:shimmer/shimmer.dart';
@@ -6,7 +6,6 @@ import 'package:injector/injector.dart';
import 'package:intl/intl.dart'; import 'package:intl/intl.dart';
import 'package:professional_repository/professional_repository.dart'; import 'package:professional_repository/professional_repository.dart';
import 'package:prosappco/blocs/service_bloc/service_bloc.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:prosappco/screens/service/user_service_screen.dart';
import 'package:service_repository/service_repository.dart'; import 'package:service_repository/service_repository.dart';
import 'package:shimmer/shimmer.dart'; import 'package:shimmer/shimmer.dart';
+10 -3
View File
@@ -319,9 +319,16 @@ class _ScoreScreenState extends State<ScoreScreen> {
BlocProvider.of<ScoreBloc>(context) BlocProvider.of<ScoreBloc>(context)
.add(SendScoreEvent(comment: comment)); .add(SendScoreEvent(comment: comment));
context if (widget.service.userId ==
.read<ServiceBloc>() FirebaseAuth.instance.currentUser!.uid) {
.add(UpdateProfessionalScored(widget.service.id!)); context
.read<ServiceBloc>()
.add(UpdateProfessionalScored(widget.service.id!));
} else {
context
.read<ServiceBloc>()
.add(UpdateUserScored(widget.service.id!));
}
Navigator.pop(context); Navigator.pop(context);
}, },
+21 -13
View File
@@ -20,12 +20,16 @@ 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 =
Injector.appInstance.get<FirebaseServiceRepository>();
SettingEntity? settings; SettingEntity? settings;
DateTime today = DateTime.now(); DateTime today = DateTime.now();
DateTime now = DateTime.now(); DateTime now = DateTime.now();
late int numDay; late int numDay;
List<ServiceEntity>? _services;
CalendarFormat _calendarFormat = CalendarFormat.month; CalendarFormat _calendarFormat = CalendarFormat.month;
@override @override
@@ -36,6 +40,7 @@ class UserCalendarScreenState extends State<UserCalendarScreen> {
numDay = today.weekday; numDay = today.weekday;
_loadSettings(); _loadSettings();
_loadServices();
} }
void _loadSettings() { void _loadSettings() {
@@ -46,6 +51,17 @@ class UserCalendarScreenState extends State<UserCalendarScreen> {
); );
} }
void _loadServices() {
serviceRepository
.getServicesForProfessionalforCalendar(
widget.userProfessional.myUser.id)
.then((services) {
setState(() {
_services = services;
});
});
}
void _onDaySelected(DateTime day, DateTime focusedDay) { void _onDaySelected(DateTime day, DateTime focusedDay) {
setState(() { setState(() {
today = day; today = day;
@@ -164,7 +180,7 @@ class UserCalendarScreenState extends State<UserCalendarScreen> {
schedule.range2Hour2!, schedule.range2Hour2!,
); );
return rangesItemList(ranges, null); return rangesItemList(ranges, _services);
} }
List<TimeOfDay> ranges1 = TimeOfDayUtils.genRanges( List<TimeOfDay> ranges1 = TimeOfDayUtils.genRanges(
@@ -177,23 +193,15 @@ class UserCalendarScreenState extends State<UserCalendarScreen> {
); );
return [ return [
...rangesItemList(ranges1, null), ...rangesItemList(ranges1, _services),
...rangesItemList(ranges2, null), ...rangesItemList(ranges2, _services),
]; ];
} }
bool _isHora1Ocupada(TimeOfDay hora1, List<ServiceEntity>? events) { bool _isHora1Ocupada(TimeOfDay hora1, List<ServiceEntity>? events) {
if (events != null) { if (events != null) {
for (ServiceEntity event in events) { for (ServiceEntity event in events) {
DateTime time1 = DateTime( if (hora1 == event.range1Hour1) {
today.year,
today.month,
today.day,
hora1.hour,
hora1.minute,
);
if (event.range1Hour1 == time1.toString()) {
return true; return true;
} }
} }
@@ -238,7 +246,7 @@ class UserCalendarScreenState extends State<UserCalendarScreen> {
), ),
), ),
title: Text( title: Text(
time.format(context), ScheduleEntity.getFormatTime(time) ?? '',
style: const TextStyle(fontSize: 15, fontWeight: FontWeight.bold), style: const TextStyle(fontSize: 15, fontWeight: FontWeight.bold),
), ),
subtitle: const Text( subtitle: const Text(
-2
View File
@@ -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/professional_list_bloc/professional_list_bloc.dart';
import 'package:prosappco/blocs/service_bloc/service_bloc.dart'; import 'package:prosappco/blocs/service_bloc/service_bloc.dart';
import 'package:prosappco/constansts.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/lists/professional_list_screen.dart';
import 'package:prosappco/screens/user/user_service_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:prosappco/utils/time_of_day_extension.dart';
import 'package:service_repository/service_repository.dart'; import 'package:service_repository/service_repository.dart';
import 'package:setting_repository/setting_repository.dart'; import 'package:setting_repository/setting_repository.dart';
@@ -48,6 +48,17 @@ class FirebaseServiceRepository {
.toList()); .toList());
} }
Future<List<ServiceEntity>> getServicesForProfessionalforCalendar(
String professionalId) async {
QuerySnapshot<Map<String, dynamic>> 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<List<ServiceEntity>> getServicesHistoryForUser(String userId) { Stream<List<ServiceEntity>> getServicesHistoryForUser(String userId) {
return serviceCollection return serviceCollection
.where('user_id', isEqualTo: userId) .where('user_id', isEqualTo: userId)
@@ -69,6 +80,17 @@ class FirebaseServiceRepository {
.toList()); .toList());
} }
Stream<List<ServiceEntity>> 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 // actualizar professionalScored a true
Future<void> setProfessionalScored(String serviceId) { Future<void> setProfessionalScored(String serviceId) {
@@ -80,17 +102,6 @@ class FirebaseServiceRepository {
Future<void> setUserScored(String serviceId) { Future<void> setUserScored(String serviceId) {
return serviceCollection.doc(serviceId).update({'user_scored': true}); return serviceCollection.doc(serviceId).update({'user_scored': true});
} }
// TODO: NEW
Stream<List<ServiceEntity>> 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());
}
} }