import 'dart:async'; 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/components/general_secondary_button.dart'; import 'package:prosappco/local_notifications/local_notifications.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:user_repository/user_repository.dart'; class UserServiceScreen extends StatefulWidget { final String serviceId; const UserServiceScreen({super.key, required this.serviceId}); @override State createState() => _UserServiceScreenState(); } class _UserServiceScreenState extends State { final settingRepository = Injector.appInstance.get(); SettingEntity? settings; Timer? _timer; @override void initState() { super.initState(); _loadSettings(); _startTimer(); } @override void dispose() { _timer?.cancel(); super.dispose(); } void _startTimer() { _timer = Timer.periodic(const Duration(minutes: 5), (timer) { setState(() {}); }); } void _loadSettings() { settingRepository.getSettings().then( (value) => setState(() { settings = value; }), ); } @override Widget build(BuildContext context) { return BlocProvider( create: (context) => Injector.appInstance.get(), child: Scaffold( appBar: AppBar( title: const Text('Servicio'), ), body: BlocBuilder( builder: (context, state) { if (state is ServiceLoaded) { final service = state.service; return FutureBuilder( future: _getUserAndProfessionalInfo(service), builder: (BuildContext 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; final professionalInfo = snapshot.data![1] as ProfessionalEntity; return Column( children: [ 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: Row( crossAxisAlignment: CrossAxisAlignment.start, children: [ Text( '${userInfo.name}', 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: const Text('Rating: 5.0'), ), Container( margin: const EdgeInsets.only( left: 40, right: 40, top: 20, bottom: 20), padding: const EdgeInsets.symmetric( horizontal: 20, vertical: 15), decoration: BoxDecoration( color: const Color(0xFFD6F4FF), borderRadius: BorderRadius.circular(20), boxShadow: [ BoxShadow( color: Colors.grey.withOpacity(0.5), spreadRadius: 1, blurRadius: 5, offset: const Offset(1, 3), ), ], ), child: Row( children: [ const Icon( Icons.error_outline, size: 27, color: Colors.black54, ), const SizedBox(width: 15), service.location == ServiceLocationPreferences.delivery ? const Text( 'Servicio a su domicilio.', style: TextStyle( color: Colors.black, fontSize: 14), ) : const Text( 'Servicio en sitio / consultorio', style: TextStyle( color: Colors.black, fontSize: 14), ), ], ), ), Visibility( visible: settings?.tarifas ?? false, child: Column( children: [ Text( formatCurrency( int.tryParse(service.rate) ?? 0), style: const TextStyle( fontWeight: FontWeight.w600, fontSize: 25), ), const Text('Tarifa de consulta'), const SizedBox(height: 15), const Text( 'Metodos de pago', style: TextStyle( fontSize: 18, fontWeight: FontWeight.bold, ), ), professionalInfo.paymentMethods.datafono || professionalInfo.paymentMethods.nequi || professionalInfo .paymentMethods.transferencia ? Wrap( spacing: 8, runSpacing: 8, alignment: WrapAlignment.center, children: [ Visibility( visible: professionalInfo .paymentMethods.datafono, child: const Chip( label: Text('Datafono')), ), Visibility( visible: professionalInfo .paymentMethods.nequi, child: const Chip( label: Text('Nequi')), ), Visibility( visible: professionalInfo .paymentMethods.transferencia, child: const Chip( label: Text( 'Transferencia Bancaria')), ), ], ) : const Text( 'No hay metodos de pago registrados', overflow: TextOverflow.ellipsis, style: TextStyle( fontSize: 15, color: Colors.black45, ), ), ], ), ), ListTile( leading: const Icon(Icons.near_me), title: Text( service.address, style: TextStyle( fontSize: 15, color: Colors.grey[600]), ), subtitle: service.aditionalAddress.isEmpty ? null : Text( service.aditionalAddress, style: TextStyle( fontSize: 15, color: Colors.grey[600]), ), ), service.description.isEmpty ? const SizedBox() : Container( alignment: Alignment.center, width: MediaQuery.of(context).size.width * 0.8, child: Text( '"${service.description.trim()}"', style: TextStyle( color: Colors.grey[600], fontStyle: FontStyle.italic, ), ), ), Expanded( child: customMessageStatus(service), ), const Divider( height: 1, thickness: 0.5, ), Padding( padding: const EdgeInsets.symmetric( vertical: 10, horizontal: 15, ), child: customButton(service, context, userInfo), ), ], ); } } }, ); } else { BlocProvider.of(context) .add(LoadService(widget.serviceId)); return const Center( child: CircularProgressIndicator(), ); } }, ), ), ); } Widget customMessageStatus(ServiceEntity service) { DateTime serviceDate = DateTime.parse(service.day); DateTime now = DateTime.now(); DateTime serviceDateTime = DateTime( serviceDate.year, serviceDate.month, serviceDate.day, service.range1Hour2.hour, service.range1Hour2.minute, ); if (now.isAfter(serviceDateTime)) { return Stack( alignment: AlignmentDirectional.topCenter, clipBehavior: Clip.none, children: [ Card( color: Colors.red.shade100, child: const Padding( padding: EdgeInsets.fromLTRB(32, 56, 32, 32), child: Column( mainAxisAlignment: MainAxisAlignment.center, children: [ Text( 'Caducado', style: TextStyle(fontSize: 32, color: Colors.red), ), Text( 'Tu servicio ha excedido \n el tiempo de espera', textAlign: TextAlign.center, style: TextStyle(fontSize: 18, color: Colors.red), ), ], ), ), ), Positioned( top: -40, child: Container( padding: const EdgeInsets.all(16), decoration: BoxDecoration( color: Colors.white, border: Border.all(color: Colors.red.shade100, width: 4), shape: BoxShape.circle, ), child: const Icon(Icons.close_rounded, color: Colors.red, size: 48), ), ) ], ); } else { if (service.status == ServiceStatus.cancelled) { return Stack( alignment: AlignmentDirectional.topCenter, clipBehavior: Clip.none, children: [ Card( color: Colors.red.shade100, child: const Padding( padding: EdgeInsets.fromLTRB(32, 56, 32, 32), child: Text( 'Cancelado', style: TextStyle(fontSize: 32, color: Colors.red), ), ), ), Positioned( top: -40, child: Container( padding: const EdgeInsets.all(16), decoration: BoxDecoration( color: Colors.white, border: Border.all(color: Colors.red.shade100, width: 4), shape: BoxShape.circle, ), child: const Icon(Icons.close_rounded, color: Colors.red, size: 48), ), ) ], ); } if (service.status == ServiceStatus.denied) { return Stack( alignment: AlignmentDirectional.topCenter, clipBehavior: Clip.none, children: [ Card( color: Colors.red.shade100, child: const Padding( padding: EdgeInsets.fromLTRB(32, 56, 32, 32), child: Text( 'Rechazado', style: TextStyle(fontSize: 32, color: Colors.red), ), ), ), Positioned( top: -40, child: Container( padding: const EdgeInsets.all(16), decoration: BoxDecoration( color: Colors.white, border: Border.all(color: Colors.red.shade100, width: 4), shape: BoxShape.circle, ), child: const Icon(Icons.close_rounded, color: Colors.red, size: 48), ), ) ], ); } if (service.status == ServiceStatus.completed && service.userScored == false) { return Stack( alignment: AlignmentDirectional.topCenter, clipBehavior: Clip.none, children: [ Card( color: Colors.green.shade50, child: Padding( padding: const EdgeInsets.fromLTRB(32, 56, 32, 32), child: Column( children: [ const Text( 'Completado', style: TextStyle(fontSize: 32, color: Colors.green), ), const Text( 'Califica el servicio', style: TextStyle(fontSize: 20, color: Colors.green), ), const SizedBox(height: 15), FilledButton( onPressed: () { Navigator.push( context, CupertinoPageRoute( builder: (context) => ScoreScreen( service: service, ), ), ); }, style: FilledButton.styleFrom( backgroundColor: Colors.green, padding: const EdgeInsets.symmetric(vertical: 15), shape: RoundedRectangleBorder( borderRadius: BorderRadius.circular(10), ), ), child: Container( alignment: Alignment.center, width: MediaQuery.of(context).size.width * 0.4, child: const Text( 'Calificar', style: TextStyle( color: Colors.white, fontSize: 18, ), ), ), ), ], ), ), ), 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, ), ), ) ], ); } if (service.status == ServiceStatus.completed && service.userScored == true) { 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: Column( children: [ 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, ), child: const Icon( Icons.star, color: Colors.green, size: 48, ), ), ) ], ); } } return const SizedBox(); } Widget customButton( ServiceEntity service, BuildContext context, MyUser userInfo) { DateTime serviceDate = DateTime.parse(service.day); DateTime now = DateTime.now(); DateTime serviceDateTime = DateTime( serviceDate.year, serviceDate.month, serviceDate.day, service.range1Hour2.hour, service.range1Hour2.minute, ); if (now.isAfter(serviceDateTime)) { return GeneralSecondaryButton( label: 'Volver', onPressed: () { Navigator.pop(context); }, ); } else { if (service.status == ServiceStatus.pending) { return GeneralSecondaryButton( label: 'Cancelar servicio', color: Theme.of(context).colorScheme.error, onPressed: () { final currentState = context.read().state; if (currentState is ServiceLoaded) { context.read().add(UpdateServiceStatus( widget.serviceId, ServiceStatus.cancelled)); } if (userInfo.token != null) { LocalNotifications.sendPushNotification( userInfo.token!, 'Servicio cancelado', 'Servicio cancelado por ${userInfo.name}', ); } }, ); } if (service.status == ServiceStatus.acepted) { if (serviceDateTime.difference(now).inDays > 1) { return GeneralSecondaryButton( label: 'Cancelar servicio', color: Theme.of(context).colorScheme.error, onPressed: () { final currentState = context.read().state; if (currentState is ServiceLoaded) { context.read().add(UpdateServiceStatus( widget.serviceId, ServiceStatus.cancelled)); } if (userInfo.token != null) { LocalNotifications.sendPushNotification( userInfo.token!, 'Servicio cancelado', 'Servicio cancelado por ${userInfo.name}', ); } }, ); } } if (service.status == ServiceStatus.active) { return GeneralSecondaryButton( label: 'Terminar servicio', onPressed: () { final currentState = context.read().state; if (currentState is ServiceLoaded) { context.read().add(UpdateServiceStatus( widget.serviceId, ServiceStatus.completed)); } }); } } return GeneralSecondaryButton( label: 'Volver', onPressed: () { Navigator.pop(context); }, ); } String formatCurrency(int number) { final formatter = NumberFormat.currency(locale: 'es_CO', decimalDigits: 0, symbol: ''); return '\$${formatter.format(number)}'; } Future> _getUserAndProfessionalInfo( ServiceEntity service) async { final userRepo = Injector.appInstance.get(); final userInfo = await userRepo.getMyUser(service.professionalId); final professionalRepo = Injector.appInstance.get(); final professionalInfo = await professionalRepo.getProInfo(service.professionalId); return [userInfo, professionalInfo]; } }