diff --git a/lib/app.dart b/lib/app.dart index bf011e6..e8cbdcf 100644 --- a/lib/app.dart +++ b/lib/app.dart @@ -6,7 +6,7 @@ import 'package:prosappco/blocs/my_user_bloc/my_user_bloc.dart'; import 'package:prosappco/blocs/professional_bloc/professional_bloc.dart'; import 'package:prosappco/blocs/professional_profile_bloc/professional_profile_bloc.dart'; import 'package:prosappco/blocs/profile_bloc/profile_bloc.dart'; -import 'package:prosappco/blocs/service_bloc/service_bloc.dart'; +import 'package:prosappco/blocs/score_bloc/score_bloc.dart'; import 'app_view.dart'; @@ -23,6 +23,9 @@ class MainApp extends StatelessWidget { BlocProvider( create: (context) => Injector.appInstance.get(), ), + BlocProvider( + create: (context) => Injector.appInstance.get(), + ), BlocProvider( create: (context) => Injector.appInstance.get(), ), diff --git a/lib/blocs/score_bloc/score_bloc.dart b/lib/blocs/score_bloc/score_bloc.dart new file mode 100644 index 0000000..e4cecd0 --- /dev/null +++ b/lib/blocs/score_bloc/score_bloc.dart @@ -0,0 +1,41 @@ +import 'dart:developer'; + +import 'package:bloc/bloc.dart'; +import 'package:equatable/equatable.dart'; +import 'package:score_repository/score_repository.dart'; + +part 'score_event.dart'; +part 'score_state.dart'; + +class ScoreBloc extends Bloc { + final FirebaseScoreRepository _firebaseScoreRepository; + + ScoreBloc({ + required FirebaseScoreRepository firebaseScoreRepository, + }) : _firebaseScoreRepository = firebaseScoreRepository, + super(ScoreInitial()) { + try { + emit(ScoreSuccess(_firebaseScoreRepository.getReputation())); + } catch (e) { + log("siteriespierdes1" + e.toString()); + } + _firebaseScoreRepository.streamReputation().listen((event) { + try { + emit(ScoreSuccess(event)); + } catch (e) { + // log + log("siteriespierdes2" + e.toString()); + } + }); + + on(_onSendScoreEvent); + } + + void _onSendScoreEvent(SendScoreEvent event, Emitter emit) async { + try { + await _firebaseScoreRepository.addComment(event.comment); + } catch (e) { + log("siteriespierdes3" + e.toString()); + } + } +} diff --git a/lib/blocs/score_bloc/score_event.dart b/lib/blocs/score_bloc/score_event.dart new file mode 100644 index 0000000..34805c3 --- /dev/null +++ b/lib/blocs/score_bloc/score_event.dart @@ -0,0 +1,17 @@ +part of 'score_bloc.dart'; + +abstract class ScoreEvent extends Equatable { + const ScoreEvent(); + + @override + List get props => []; +} + +class SendScoreEvent extends ScoreEvent { + final CommentEntity comment; + + const SendScoreEvent({required this.comment}); + + @override + List get props => [comment]; +} diff --git a/lib/blocs/score_bloc/score_state.dart b/lib/blocs/score_bloc/score_state.dart new file mode 100644 index 0000000..8ac7657 --- /dev/null +++ b/lib/blocs/score_bloc/score_state.dart @@ -0,0 +1,22 @@ +part of 'score_bloc.dart'; + +abstract class ScoreState extends Equatable { + const ScoreState(); + + @override + List get props => []; +} + +class ScoreInitial extends ScoreState {} + +class ScoreFailure extends ScoreState {} + +class ScoreLoading extends ScoreState {} + +class ScoreSuccess extends ScoreState { + final ReputationEntity reputation; + const ScoreSuccess(this.reputation); + + @override + List get props => [reputation]; +} diff --git a/lib/components/drawer_reputation.dart b/lib/components/drawer_reputation.dart new file mode 100644 index 0000000..71be926 --- /dev/null +++ b/lib/components/drawer_reputation.dart @@ -0,0 +1,23 @@ +import 'package:flutter/material.dart'; +import 'package:flutter_bloc/flutter_bloc.dart'; +import 'package:prosappco/blocs/score_bloc/score_bloc.dart'; +import 'package:score_repository/score_repository.dart'; + +class DrawerReputation extends StatelessWidget { + final Widget Function(ReputationEntity) builder; + + const DrawerReputation({super.key, required this.builder}); + + @override + Widget build(BuildContext context) { + return BlocBuilder(builder: (context, state) { + if (state is ScoreSuccess) { + return builder(state.reputation); + } else if (state is ScoreFailure) { + return const Center(child: Text('Error')); + } else { + return const Center(child: CircularProgressIndicator()); + } + }); + } +} diff --git a/lib/components/general_drawer.dart b/lib/components/general_drawer.dart index 04da6d7..bd24305 100644 --- a/lib/components/general_drawer.dart +++ b/lib/components/general_drawer.dart @@ -1,9 +1,14 @@ +import 'dart:developer'; + +import 'package:cloud_firestore/cloud_firestore.dart'; import 'package:flutter/cupertino.dart'; import 'package:flutter/foundation.dart'; import 'package:flutter/material.dart'; import 'package:flutter_bloc/flutter_bloc.dart'; +import 'package:flutter_rating_bar/flutter_rating_bar.dart'; import 'package:prosappco/blocs/my_user_bloc/my_user_bloc.dart'; import 'package:prosappco/blocs/professional_bloc/professional_bloc.dart'; +import 'package:prosappco/components/drawer_reputation.dart'; import 'package:prosappco/components/general_drawer_header.dart'; import 'package:prosappco/components/general_drawer_item.dart'; import 'package:prosappco/screens/configuration/configuration_screen.dart'; @@ -209,6 +214,51 @@ class GeneralDrawer extends StatelessWidget { ), ), ), + DrawerReputation(builder: (reputation) { + final isProModeActive = + (professionalState is LoadedModeProState) && + professionalState.isProModeActive; + + final total = isProModeActive + ? reputation.totalPro + : reputation.total; + + final average = isProModeActive + ? reputation.averagePro + : reputation.average; + + 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( + children: [ + RatingBar.builder( + initialRating: calculoRating(average), + minRating: 1, + direction: Axis.horizontal, + allowHalfRating: true, + itemCount: 5, + itemSize: 25, + maxRating: 5, + itemBuilder: (context, _) => const Icon( + Icons.star, + color: Color(0xFF2BA4EC), + ), + onRatingUpdate: (rating) {}, + ignoreGestures: true, + ), + const SizedBox(width: 5), + Text( + '${average.toStringAsFixed(1)} (${total.toString()})', + ), + ], + )); + }), Padding( padding: const EdgeInsets.only(top: 10), child: Text( @@ -246,6 +296,23 @@ class GeneralDrawer extends StatelessWidget { ); } + double calculoRating(double average) { + String numeroString = average.toString(); + List partes = numeroString.split('.'); + int parteEntera = int.parse(partes[0]); + int parteFraccionaria = partes.length > 1 ? int.parse(partes[1]) : 0; + + if (parteFraccionaria >= 3) { + parteFraccionaria = 5; + } else { + parteFraccionaria = 0; + } + + // Unir la parte entera y fraccionaria y convertirlo nuevamente a double + double resultado = double.parse('$parteEntera.$parteFraccionaria'); + return resultado; + } + buttonOfState(BuildContext context, ProfessionalState state) { return ElevatedButton( onPressed: () { diff --git a/lib/components/general_reputation.dart b/lib/components/general_reputation.dart new file mode 100644 index 0000000..4c1dc3b --- /dev/null +++ b/lib/components/general_reputation.dart @@ -0,0 +1,31 @@ +import 'package:flutter/material.dart'; +import 'package:injector/injector.dart'; +import 'package:score_repository/score_repository.dart'; + +class GeneralReputation extends StatelessWidget { + final String userId; + final Widget Function(BuildContext, ReputationEntity) builder; + + const GeneralReputation({ + super.key, + required this.userId, + required this.builder, + }); + + @override + Widget build(BuildContext context) { + final repository = Injector.appInstance.get(); + return FutureBuilder( + future: repository.getReputationByUserId(userId), + builder: (context, snapshot) { + if (snapshot.connectionState == ConnectionState.waiting) { + return const Center(child: CircularProgressIndicator()); + } else if (snapshot.hasError) { + return Text('Error: ${snapshot.error}'); + } else { + final reputation = snapshot.data!; + return builder(context, reputation); + } + }); + } +} diff --git a/lib/dependency/app_di.dart b/lib/dependency/app_di.dart index 73a3660..3c0b14f 100644 --- a/lib/dependency/app_di.dart +++ b/lib/dependency/app_di.dart @@ -11,10 +11,12 @@ import 'package:prosappco/blocs/professional_bloc/professional_bloc.dart'; import 'package:prosappco/blocs/professional_list_bloc/professional_list_bloc.dart'; import 'package:prosappco/blocs/professional_profile_bloc/professional_profile_bloc.dart'; import 'package:prosappco/blocs/profile_bloc/profile_bloc.dart'; +import 'package:prosappco/blocs/score_bloc/score_bloc.dart'; import 'package:prosappco/blocs/service_bloc/service_bloc.dart'; import 'package:prosappco/blocs/setting_bloc/setting_bloc.dart'; import 'package:prosappco/blocs/sign_up_bloc/sign_up_bloc.dart'; import 'package:prosappco/blocs/sing_in_bloc/sign_in_bloc.dart'; +import 'package:score_repository/score_repository.dart'; import 'package:service_repository/service_repository.dart'; import 'package:user_repository/user_repository.dart'; import 'package:city_repository/city_repository.dart'; @@ -38,13 +40,15 @@ class AppDI { injector.registerSingleton(() => FirebaseProfessionalRepository()); injector.registerSingleton(() => FirebaseServiceRepository()); injector.registerSingleton(() => FirebaseChatRepository()); + injector.registerSingleton(() => FirebaseScoreRepository()); injector.registerSingleton((() => AuthenticationBloc(myUserRepository: injector.get()))); injector.registerSingleton( (() => MyUserBloc(myUserRepository: injector.get()))); - + injector.registerSingleton( + (() => ScoreBloc(firebaseScoreRepository: injector.get()))); injector.registerSingleton( (() => ProfileBloc(userRepository: injector.get()))); diff --git a/lib/screens/chat/chat_screen.dart b/lib/screens/chat/chat_screen.dart index cc7916f..3792b62 100644 --- a/lib/screens/chat/chat_screen.dart +++ b/lib/screens/chat/chat_screen.dart @@ -59,8 +59,7 @@ class _ChatScreenState extends State { children: [ FutureBuilder( future: _getUserAndProfessionalInfo(widget.service), - builder: (BuildContext context, - AsyncSnapshot> snapshot) { + builder: (BuildContext context, AsyncSnapshot> snapshot) { if (snapshot.connectionState == ConnectionState.waiting) { return const Center(child: CircularProgressIndicator()); } else { diff --git a/lib/screens/score/score_screen.dart b/lib/screens/score/score_screen.dart new file mode 100644 index 0000000..d4177f4 --- /dev/null +++ b/lib/screens/score/score_screen.dart @@ -0,0 +1,228 @@ +import 'package:cloud_firestore/cloud_firestore.dart'; +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:flutter_rating_bar/flutter_rating_bar.dart'; +import 'package:prosappco/blocs/score_bloc/score_bloc.dart'; +import 'package:score_repository/score_repository.dart'; +import 'package:service_repository/service_repository.dart'; +import 'package:user_repository/user_repository.dart'; + +class ScoreScreen extends StatefulWidget { + final ServiceEntity service; + + const ScoreScreen({Key? key, required this.service}) : super(key: key); + + @override + State createState() => _ScoreScreenState(); +} + +class _ScoreScreenState extends State { + double _rating = 1.0; + TextEditingController commentController = TextEditingController(); + late Future> _userInfoFuture; + + @override + void initState() { + super.initState(); + _userInfoFuture = _getUserAndProfessionalInfo(widget.service); + } + + @override + Widget build(BuildContext context) { + return Scaffold( + appBar: AppBar( + title: const Text('Calificación'), + ), + body: Column( + children: [ + Expanded( + child: SingleChildScrollView( + child: Column( + children: [ + FutureBuilder( + future: _userInfoFuture, + builder: (context, AsyncSnapshot> snapshot) { + if (snapshot.connectionState == ConnectionState.waiting) { + return const Center(child: CircularProgressIndicator()); + } else { + if (snapshot.hasError) { + return Center( + child: Text('Error inesperado: ${snapshot.error}'), + ); + } else { + final userInfo = snapshot.data![0] as MyUser; + + return Padding( + padding: const EdgeInsets.symmetric(horizontal: 15), + child: ListTile( + leading: Container( + width: 60, + height: 60, + decoration: BoxDecoration( + color: Colors.grey.shade300, + shape: BoxShape.circle, + image: userInfo.picture == null + ? null + : DecorationImage( + image: NetworkImage( + userInfo.picture ?? ''), + fit: BoxFit.contain, + ), + ), + child: userInfo.picture == null + ? Icon( + CupertinoIcons.person, + color: Colors.grey.shade400, + size: 40, + ) + : null, + ), + title: Text( + userInfo.name ?? '', + overflow: TextOverflow.ellipsis, + style: const TextStyle( + fontSize: 15, + fontWeight: FontWeight.w600, + ), + ), + subtitle: const Text( + 'Rating: 5.0', + style: TextStyle( + fontSize: 13, + color: Colors.blue, + ), + ), + ), + ); + } + } + }, + ), + const SizedBox(height: 10), + const Text( + 'Calificación', + style: TextStyle(fontSize: 20, fontWeight: FontWeight.w600), + ), + const SizedBox(height: 10), + RatingBar.builder( + initialRating: _rating, + minRating: 1, + direction: Axis.horizontal, + allowHalfRating: true, + itemCount: 5, + itemSize: 40, + glow: false, + maxRating: 5, + itemPadding: const EdgeInsets.symmetric(horizontal: 5), + itemBuilder: (context, _) => const Icon( + Icons.star, + color: Color(0xFF2BA4EC), + ), + onRatingUpdate: (rating) { + setState(() { + _rating = rating; + }); + }, + ignoreGestures: false, + ), + Padding( + padding: const EdgeInsets.symmetric( + horizontal: 40, vertical: 40), + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + const Text( + 'Comentario', + style: TextStyle( + fontSize: 20, fontWeight: FontWeight.w600), + ), + TextFormField( + maxLines: null, + maxLength: 400, + keyboardType: TextInputType.multiline, + controller: commentController, + ), + ], + ), + ), + ], + ), + ), + ), + const Divider( + height: 1, + thickness: 0.5, + ), + Padding( + padding: const EdgeInsets.symmetric( + vertical: 10, + horizontal: 15, + ), + child: FilledButton( + onPressed: () { + final CommentEntity comment = widget.service.userId == + FirebaseAuth.instance.currentUser!.uid + ? CommentEntity( + serviceId: widget.service.id!, + authorId: FirebaseAuth.instance.currentUser!.uid, + isFromUser: true, + score: _rating, + destinationId: widget.service.professionalId, + content: commentController.text, + createdAt: Timestamp.now(), + ) + : CommentEntity( + serviceId: widget.service.id!, + authorId: FirebaseAuth.instance.currentUser!.uid, + isFromUser: false, + score: _rating, + destinationId: widget.service.userId, + content: commentController.text, + createdAt: Timestamp.now(), + ); + + BlocProvider.of(context) + .add(SendScoreEvent(comment: comment)); + }, + style: FilledButton.styleFrom( + backgroundColor: Theme.of(context).colorScheme.primary, + padding: const EdgeInsets.symmetric(vertical: 15), + shape: RoundedRectangleBorder( + borderRadius: BorderRadius.circular(10), + ), + ), + child: Container( + alignment: Alignment.center, + width: double.infinity, + child: const Text( + 'Enviar calificación', + style: TextStyle( + color: Colors.white, + fontSize: 18, + ), + ), + ), + ), + ), + ], + ), + ); + } + + Future> _getUserAndProfessionalInfo( + ServiceEntity service) async { + final userRepo = FirebaseUserRepository(FirebaseAuth.instance); + + final MyUser? userInfo; + + if (FirebaseAuth.instance.currentUser!.uid == service.userId) { + userInfo = await userRepo.getMyUser(service.userId); + } else { + userInfo = await userRepo.getMyUser(service.professionalId); + } + + return [userInfo]; + } +} diff --git a/lib/screens/service/professional_service_screen.dart b/lib/screens/service/professional_service_screen.dart index f57deea..847d179 100644 --- a/lib/screens/service/professional_service_screen.dart +++ b/lib/screens/service/professional_service_screen.dart @@ -9,6 +9,7 @@ import 'package:professional_repository/professional_repository.dart'; import 'package:prosappco/blocs/service_bloc/service_bloc.dart'; import 'package:prosappco/components/general_secondary_button.dart'; import 'package:prosappco/screens/chat/chat_screen.dart'; +import 'package:prosappco/screens/score/score_screen.dart'; import 'package:service_repository/service_repository.dart'; import 'package:setting_repository/setting_repository.dart'; import 'package:url_launcher/url_launcher.dart'; @@ -56,7 +57,8 @@ class _ProfessionalServiceScreenState extends State { final service = state.service; return FutureBuilder( future: _getUserAndProfessionalInfo(service), - builder: (BuildContext context, AsyncSnapshot> snapshot) { + builder: (BuildContext context, + AsyncSnapshot> snapshot) { if (snapshot.connectionState == ConnectionState.waiting) { return const Center(child: CircularProgressIndicator()); } else { @@ -515,73 +517,91 @@ class _ProfessionalServiceScreenState extends State { ); } - // if (service.status == ServiceStatus.active) { - // return Stack( - // alignment: AlignmentDirectional.topCenter, - // clipBehavior: Clip.none, - // children: [ - // Card( - // color: Colors.green.shade50, - // child: const Padding( - // padding: EdgeInsets.fromLTRB(32, 56, 32, 32), - // child: Text( - // 'En proceso...', - // style: TextStyle(fontSize: 32, color: Colors.green), - // ), - // ), - // ), - // Positioned( - // top: -40, - // child: Container( - // padding: const EdgeInsets.all(16), - // decoration: BoxDecoration( - // color: Colors.white, - // border: Border.all(color: Colors.green.shade50, width: 4), - // shape: BoxShape.circle, - // ), - // child: const Icon( - // Icons.access_time_rounded, - // color: Colors.green, - // size: 48, - // ), - // ), - // ) - // ], - // ); - // } if (service.status == ServiceStatus.completed) { - return Stack( - alignment: AlignmentDirectional.topCenter, - clipBehavior: Clip.none, - children: [ - Card( - color: Colors.green.shade50, - child: const Padding( - padding: EdgeInsets.fromLTRB(32, 56, 32, 32), - child: Text( - 'Completado', - style: TextStyle(fontSize: 32, color: Colors.green), + if (service.userScored) { + return Stack( + alignment: AlignmentDirectional.topCenter, + clipBehavior: Clip.none, + children: [ + Card( + color: Colors.green.shade50, + child: const Padding( + padding: EdgeInsets.fromLTRB(32, 56, 32, 32), + child: Text( + 'Completado', + style: TextStyle(fontSize: 32, color: Colors.green), + ), ), ), - ), - Positioned( - top: -40, - child: Container( - padding: const EdgeInsets.all(16), - decoration: BoxDecoration( - color: Colors.white, - border: Border.all(color: Colors.green.shade50, width: 4), - shape: BoxShape.circle, + Positioned( + top: -40, + child: Container( + padding: const EdgeInsets.all(16), + decoration: BoxDecoration( + color: Colors.white, + border: Border.all(color: Colors.green.shade50, width: 4), + shape: BoxShape.circle, + ), + child: const Icon( + Icons.check_rounded, + color: Colors.green, + size: 48, + ), ), - child: const Icon( - Icons.check_rounded, - color: Colors.green, - size: 48, + ) + ], + ); + } else { + return Stack( + alignment: AlignmentDirectional.topCenter, + clipBehavior: Clip.none, + children: [ + Card( + color: Colors.green.shade50, + child: Padding( + padding: EdgeInsets.fromLTRB(32, 56, 32, 32), + child: Column( + children: [ + Text( + 'Completado', + style: TextStyle(fontSize: 32, color: Colors.green), + ), + SizedBox(height: 16), + FilledButton( + onPressed: () { + Navigator.push( + context, + CupertinoPageRoute( + builder: (context) => ScoreScreen( + service: service, + ), + ), + ); + }, + child: Text('Calificar')) + ], + ), ), ), - ) - ], - ); + Positioned( + top: -40, + child: Container( + padding: const EdgeInsets.all(16), + decoration: BoxDecoration( + color: Colors.white, + border: Border.all(color: Colors.green.shade50, width: 4), + shape: BoxShape.circle, + ), + child: const Icon( + Icons.star, + color: Colors.green, + size: 48, + ), + ), + ) + ], + ); + } } return const SizedBox(); @@ -627,16 +647,6 @@ class _ProfessionalServiceScreenState extends State { } }, ); - // customStatusButton( - // label: 'Iniciar servicio', - // onPressed: () { - // final currentState = context.read().state; - // if (currentState is ServiceLoaded) { - // context.read().add( - // UpdateServiceStatus(widget.serviceId, ServiceStatus.active)); - // } - // }, - // ); } if (service.status == ServiceStatus.active) { @@ -670,7 +680,8 @@ class _ProfessionalServiceScreenState extends State { return '\$${formatter.format(number)}'; } - Future> _getUserAndProfessionalInfo(ServiceEntity service) async { + Future> _getUserAndProfessionalInfo( + ServiceEntity service) async { final userRepo = FirebaseUserRepository(FirebaseAuth.instance); final userInfo = await userRepo.getMyUser(service.userId); diff --git a/packages/score_repository/lib/score_repository.dart b/packages/score_repository/lib/score_repository.dart new file mode 100644 index 0000000..d74ac15 --- /dev/null +++ b/packages/score_repository/lib/score_repository.dart @@ -0,0 +1,4 @@ +library chat_repository; + +export 'src/entities/entities.dart'; +export 'src/repositories/firebase_score_repository.dart'; diff --git a/packages/score_repository/lib/src/entities/comment_entity.dart b/packages/score_repository/lib/src/entities/comment_entity.dart new file mode 100644 index 0000000..e4ec26d --- /dev/null +++ b/packages/score_repository/lib/src/entities/comment_entity.dart @@ -0,0 +1,70 @@ +import 'package:cloud_firestore/cloud_firestore.dart'; +import 'package:equatable/equatable.dart'; + +class CommentEntity extends Equatable { + final String authorId; + final String destinationId; + final String serviceId; + final String content; + final double score; + final bool isFromUser; + final Timestamp createdAt; + + const CommentEntity({ + required this.authorId, + required this.destinationId, + required this.serviceId, + required this.content, + required this.score, + required this.isFromUser, + required this.createdAt, + }); + + static CommentEntity fromDocument(Map doc) { + return CommentEntity( + authorId: doc['author_id'] as String, + destinationId: doc['destination_id'] as String, + serviceId: doc['service_id'] as String, + content: doc['content'] as String, + score: doc['score'] as double, + isFromUser: doc['is_from_user'] as bool, + createdAt: doc['created_at'] as Timestamp, + ); + } + + Map toDocument() { + return { + 'author_id': authorId, + 'destination_id': destinationId, + 'service_id': serviceId, + 'content': content, + 'score': score, + 'is_from_user': isFromUser, + 'created_at': createdAt, + }; + } + + @override + List get props => [ + authorId, + destinationId, + serviceId, + content, + score, + isFromUser, + createdAt, + ]; + + @override + String toString() { + return '''CommentEntity{ + authorId: $authorId, + destinationId: $destinationId, + serviceId: $serviceId, + content: $content, + score: $score, + isFromUser: $isFromUser, + createdAt: $createdAt + }'''; + } +} diff --git a/packages/score_repository/lib/src/entities/entities.dart b/packages/score_repository/lib/src/entities/entities.dart new file mode 100644 index 0000000..2084599 --- /dev/null +++ b/packages/score_repository/lib/src/entities/entities.dart @@ -0,0 +1,2 @@ +export '/src/entities/score_entity.dart'; +export '/src/entities/comment_entity.dart'; diff --git a/packages/score_repository/lib/src/entities/score_entity.dart b/packages/score_repository/lib/src/entities/score_entity.dart new file mode 100644 index 0000000..1e55c09 --- /dev/null +++ b/packages/score_repository/lib/src/entities/score_entity.dart @@ -0,0 +1,42 @@ +import 'package:cloud_firestore/cloud_firestore.dart'; +import 'package:equatable/equatable.dart'; + +class ReputationEntity extends Equatable { + final int total; + final double average; + final int totalPro; + final double averagePro; + + const ReputationEntity({ + required this.total, + required this.average, + required this.totalPro, + required this.averagePro, + }); + + @override + List get props => [total, average, totalPro, averagePro]; + + static ReputationEntity fromDocument(Map doc) { + final total = doc['total'] ?? 0; + final totalPro = doc['total_pro'] ?? 0; + final average = doc['average'] ?? 0.0; + final averagePro = doc['average_pro'] ?? 0.0; + + return ReputationEntity( + total: int.parse(total.toString()), + average: double.parse(average.toString()), + totalPro: int.parse(totalPro.toString()), + averagePro: double.parse(averagePro.toString()), + ); + } + + Map toDocument() { + return { + 'total': total, + 'average': average, + 'total_pro': totalPro, + 'average_pro': averagePro, + }; + } +} diff --git a/packages/score_repository/lib/src/repositories/firebase_score_repository.dart b/packages/score_repository/lib/src/repositories/firebase_score_repository.dart new file mode 100644 index 0000000..9e95ab9 --- /dev/null +++ b/packages/score_repository/lib/src/repositories/firebase_score_repository.dart @@ -0,0 +1,96 @@ +import 'dart:async'; +import 'dart:developer'; + +import 'package:cloud_firestore/cloud_firestore.dart'; +import 'package:firebase_auth/firebase_auth.dart'; +import 'package:score_repository/score_repository.dart'; +import 'package:score_repository/src/entities/comment_entity.dart'; + +class FirebaseScoreRepository { + final reputationsCollection = + FirebaseFirestore.instance.collection('reputations'); + + final commentsCollection = FirebaseFirestore.instance.collection('comments'); + + ReputationEntity? _reputation; + final StreamController _reputationController = + StreamController.broadcast(); + + Stream streamReputation() { + return _reputationController.stream; + } + + FirebaseScoreRepository() { + FirebaseAuth.instance.userChanges().listen((user) async { + if (user != null) { + final reputation = await getReputationByUserId(user.uid); + _reputation = reputation; + } else { + _reputation = null; + } + _reputationController.add(_reputation ?? + const ReputationEntity( + total: 0, + average: 0, + totalPro: 0, + averagePro: 0, + )); + }); + } + + ReputationEntity getReputation() { + return _reputation ?? + const ReputationEntity( + total: 0, + average: 0, + totalPro: 0, + averagePro: 0, + ); + } + + Future getReputationByUserId(String userId) async { + try { + final snap = await reputationsCollection.doc(userId).get(); + return ReputationEntity.fromDocument(snap.data()!); + } catch (e) { + log("siteriespierdes4" + e.toString()); + return const ReputationEntity( + total: 0, + average: 0, + totalPro: 0, + averagePro: 0, + ); + } + } + + addComment(CommentEntity comment) async { + await commentsCollection.add(comment.toDocument()); + + final query = await commentsCollection + .where('destination_id', isEqualTo: comment.destinationId) + .where('is_from_user', isEqualTo: comment.isFromUser) + .get(); + + var total = 0.0; + var count = 0; + for (var doc in query.docs) { + final comment = CommentEntity.fromDocument(doc.data()); + total += comment.score; + count++; + } + + if (count > 0) { + final average = total / count; + if (comment.isFromUser) { + await reputationsCollection.doc(comment.destinationId).set( + {'total_pro': count, 'average_pro': average}, + SetOptions(merge: true)); + } else { + await reputationsCollection.doc(comment.destinationId).set({ + 'total': count, + 'average': average, + }, SetOptions(merge: true)); + } + } + } +} diff --git a/packages/score_repository/pubspec.lock b/packages/score_repository/pubspec.lock new file mode 100644 index 0000000..4372f4c --- /dev/null +++ b/packages/score_repository/pubspec.lock @@ -0,0 +1,306 @@ +# Generated by pub +# See https://dart.dev/tools/pub/glossary#lockfile +packages: + _flutterfire_internals: + dependency: transitive + description: + name: _flutterfire_internals + sha256: "4eec93681221723a686ad580c2e7d960e1017cf1a4e0a263c2573c2c6b0bf5cd" + url: "https://pub.dev" + source: hosted + version: "1.3.25" + async: + dependency: transitive + description: + name: async + sha256: "947bfcf187f74dbc5e146c9eb9c0f10c9f8b30743e341481c1e2ed3ecc18c20c" + url: "https://pub.dev" + source: hosted + version: "2.11.0" + boolean_selector: + dependency: transitive + description: + name: boolean_selector + sha256: "6cfb5af12253eaf2b368f07bacc5a80d1301a071c73360d746b7f2e32d762c66" + url: "https://pub.dev" + source: hosted + version: "2.1.1" + characters: + dependency: transitive + description: + name: characters + sha256: "04a925763edad70e8443c99234dc3328f442e811f1d8fd1a72f1c8ad0f69a605" + url: "https://pub.dev" + source: hosted + version: "1.3.0" + clock: + dependency: transitive + description: + name: clock + sha256: cb6d7f03e1de671e34607e909a7213e31d7752be4fb66a86d29fe1eb14bfb5cf + url: "https://pub.dev" + source: hosted + version: "1.1.1" + cloud_firestore: + dependency: "direct main" + description: + name: cloud_firestore + sha256: "31cfa4d65d6e9ea837234fffe121304034c30c9214c06207b4a35867e3757900" + url: "https://pub.dev" + source: hosted + version: "4.15.8" + cloud_firestore_platform_interface: + dependency: transitive + description: + name: cloud_firestore_platform_interface + sha256: a0097a26569b015faf8142e159e855241609ea9a1738b5fd1c40bfe8411b41a0 + url: "https://pub.dev" + source: hosted + version: "6.1.9" + cloud_firestore_web: + dependency: transitive + description: + name: cloud_firestore_web + sha256: ed680ece29a5750985119c09cdc276b460c3a2fa80e8c12f9b7241f6b4a7ca16 + url: "https://pub.dev" + source: hosted + version: "3.10.8" + collection: + dependency: transitive + description: + name: collection + sha256: ee67cb0715911d28db6bf4af1026078bd6f0128b07a5f66fb2ed94ec6783c09a + url: "https://pub.dev" + source: hosted + version: "1.18.0" + equatable: + dependency: "direct main" + description: + name: equatable + sha256: c2b87cb7756efdf69892005af546c56c0b5037f54d2a88269b4f347a505e3ca2 + url: "https://pub.dev" + source: hosted + version: "2.0.5" + fake_async: + dependency: transitive + description: + name: fake_async + sha256: "511392330127add0b769b75a987850d136345d9227c6b94c96a04cf4a391bf78" + url: "https://pub.dev" + source: hosted + version: "1.3.1" + firebase_auth: + dependency: "direct main" + description: + name: firebase_auth + sha256: "17b841e1b000c3441b8ffceca88f468e078d0443db9643e77541bdfb7a3fd16b" + url: "https://pub.dev" + source: hosted + version: "4.17.8" + firebase_auth_platform_interface: + dependency: transitive + description: + name: firebase_auth_platform_interface + sha256: f294ceef40409a36c819a14280ca864fe487b44033e5276443377c66cb448310 + url: "https://pub.dev" + source: hosted + version: "7.1.8" + firebase_auth_web: + dependency: transitive + description: + name: firebase_auth_web + sha256: "1f231da900fe7ff9f2974f8adcbdb3363c410c24725978afa5dc33e1e7e62e06" + url: "https://pub.dev" + source: hosted + version: "5.9.8" + firebase_core: + dependency: "direct main" + description: + name: firebase_core + sha256: "53316975310c8af75a96e365f9fccb67d1c544ef0acdbf0d88bbe30eedd1c4f9" + url: "https://pub.dev" + source: hosted + version: "2.27.0" + firebase_core_platform_interface: + dependency: transitive + description: + name: firebase_core_platform_interface + sha256: c437ae5d17e6b5cc7981cf6fd458a5db4d12979905f9aafd1fea930428a9fe63 + url: "https://pub.dev" + source: hosted + version: "5.0.0" + firebase_core_web: + dependency: transitive + description: + name: firebase_core_web + sha256: c8e1d59385eee98de63c92f961d2a7062c5d9a65e7f45bdc7f1b0b205aab2492 + url: "https://pub.dev" + source: hosted + version: "2.11.5" + flutter: + dependency: "direct main" + description: flutter + source: sdk + version: "0.0.0" + flutter_lints: + dependency: "direct dev" + description: + name: flutter_lints + sha256: a25a15ebbdfc33ab1cd26c63a6ee519df92338a9c10f122adda92938253bef04 + url: "https://pub.dev" + source: hosted + version: "2.0.3" + flutter_test: + dependency: "direct dev" + description: flutter + source: sdk + version: "0.0.0" + flutter_web_plugins: + dependency: transitive + description: flutter + source: sdk + version: "0.0.0" + http_parser: + dependency: transitive + description: + name: http_parser + sha256: "2aa08ce0341cc9b354a498388e30986515406668dbcc4f7c950c3e715496693b" + url: "https://pub.dev" + source: hosted + version: "4.0.2" + js: + dependency: transitive + description: + name: js + sha256: f2c445dce49627136094980615a031419f7f3eb393237e4ecd97ac15dea343f3 + url: "https://pub.dev" + source: hosted + version: "0.6.7" + lints: + dependency: transitive + description: + name: lints + sha256: "0a217c6c989d21039f1498c3ed9f3ed71b354e69873f13a8dfc3c9fe76f1b452" + url: "https://pub.dev" + source: hosted + version: "2.1.1" + matcher: + dependency: transitive + description: + name: matcher + sha256: "1803e76e6653768d64ed8ff2e1e67bea3ad4b923eb5c56a295c3e634bad5960e" + url: "https://pub.dev" + source: hosted + version: "0.12.16" + material_color_utilities: + dependency: transitive + description: + name: material_color_utilities + sha256: "9528f2f296073ff54cb9fee677df673ace1218163c3bc7628093e7eed5203d41" + url: "https://pub.dev" + source: hosted + version: "0.5.0" + meta: + dependency: transitive + description: + name: meta + sha256: a6e590c838b18133bb482a2745ad77c5bb7715fb0451209e1a7567d416678b8e + url: "https://pub.dev" + source: hosted + version: "1.10.0" + path: + dependency: transitive + description: + name: path + sha256: "8829d8a55c13fc0e37127c29fedf290c102f4e40ae94ada574091fe0ff96c917" + url: "https://pub.dev" + source: hosted + version: "1.8.3" + plugin_platform_interface: + dependency: transitive + description: + name: plugin_platform_interface + sha256: "4820fbfdb9478b1ebae27888254d445073732dae3d6ea81f0b7e06d5dedc3f02" + url: "https://pub.dev" + source: hosted + version: "2.1.8" + sky_engine: + dependency: transitive + description: flutter + source: sdk + version: "0.0.99" + source_span: + dependency: transitive + description: + name: source_span + sha256: "53e943d4206a5e30df338fd4c6e7a077e02254531b138a15aec3bd143c1a8b3c" + url: "https://pub.dev" + source: hosted + version: "1.10.0" + stack_trace: + dependency: transitive + description: + name: stack_trace + sha256: "73713990125a6d93122541237550ee3352a2d84baad52d375a4cad2eb9b7ce0b" + url: "https://pub.dev" + source: hosted + version: "1.11.1" + stream_channel: + dependency: transitive + description: + name: stream_channel + sha256: ba2aa5d8cc609d96bbb2899c28934f9e1af5cddbd60a827822ea467161eb54e7 + url: "https://pub.dev" + source: hosted + version: "2.1.2" + string_scanner: + dependency: transitive + description: + name: string_scanner + sha256: "556692adab6cfa87322a115640c11f13cb77b3f076ddcc5d6ae3c20242bedcde" + url: "https://pub.dev" + source: hosted + version: "1.2.0" + term_glyph: + dependency: transitive + description: + name: term_glyph + sha256: a29248a84fbb7c79282b40b8c72a1209db169a2e0542bce341da992fe1bc7e84 + url: "https://pub.dev" + source: hosted + version: "1.2.1" + test_api: + dependency: transitive + description: + name: test_api + sha256: "5c2f730018264d276c20e4f1503fd1308dfbbae39ec8ee63c5236311ac06954b" + url: "https://pub.dev" + source: hosted + version: "0.6.1" + typed_data: + dependency: transitive + description: + name: typed_data + sha256: facc8d6582f16042dd49f2463ff1bd6e2c9ef9f3d5da3d9b087e244a7b564b3c + url: "https://pub.dev" + source: hosted + version: "1.3.2" + vector_math: + dependency: transitive + description: + name: vector_math + sha256: "80b3257d1492ce4d091729e3a67a60407d227c27241d6927be0130c98e741803" + url: "https://pub.dev" + source: hosted + version: "2.1.4" + web: + dependency: transitive + description: + name: web + sha256: afe077240a270dcfd2aafe77602b4113645af95d0ad31128cc02bce5ac5d5152 + url: "https://pub.dev" + source: hosted + version: "0.3.0" +sdks: + dart: ">=3.2.0 <4.0.0" + flutter: ">=3.3.0" diff --git a/packages/score_repository/pubspec.yaml b/packages/score_repository/pubspec.yaml new file mode 100644 index 0000000..57d0425 --- /dev/null +++ b/packages/score_repository/pubspec.yaml @@ -0,0 +1,26 @@ +name: score_repository +description: Dart package for the score repository. +publish_to: 'none' + +version: 1.0.11+11 + +environment: + sdk: ">=2.19.3 <3.0.0" + +dependencies: + flutter: + sdk: flutter + equatable: ^2.0.5 + + # Firebase + cloud_firestore: ^4.15.4 + firebase_core: ^2.25.4 + firebase_auth: ^4.17.4 + +dev_dependencies: + flutter_lints: ^2.0.0 + flutter_test: + sdk: flutter + +flutter: + uses-material-design: true \ No newline at end of file diff --git a/pubspec.lock b/pubspec.lock index f2e02c2..1486665 100644 --- a/pubspec.lock +++ b/pubspec.lock @@ -1025,6 +1025,13 @@ packages: url: "https://pub.dev" source: hosted version: "2.1.0" + score_repository: + dependency: "direct main" + description: + path: "packages/score_repository" + relative: true + source: path + version: "1.0.11+11" service_repository: dependency: "direct main" description: diff --git a/pubspec.yaml b/pubspec.yaml index e47d3da..302aa57 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -56,6 +56,8 @@ dependencies: path: packages/user_repository city_repository: path: packages/city_repository + score_repository: + path: packages/score_repository chat_repository: path: packages/chat_repository setting_repository: