449 lines
19 KiB
Dart
449 lines
19 KiB
Dart
import 'package:chat_repository/chat_repository.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:injector/injector.dart';
|
|
import 'package:intl/intl.dart';
|
|
import 'package:prosappco/blocs/chat_bloc/chat_bloc.dart';
|
|
import 'package:prosappco/components/general_reputation.dart';
|
|
import 'package:prosappco/local_notifications/local_notifications.dart';
|
|
import 'package:service_repository/service_repository.dart';
|
|
import 'package:user_repository/user_repository.dart';
|
|
|
|
class ChatScreen extends StatefulWidget {
|
|
final ServiceEntity service;
|
|
|
|
const ChatScreen({super.key, required this.service});
|
|
|
|
@override
|
|
State<ChatScreen> createState() => _ChatScreenState();
|
|
}
|
|
|
|
class _ChatScreenState extends State<ChatScreen> {
|
|
late final ChatBloc chatBloc;
|
|
final TextEditingController _messageController = TextEditingController();
|
|
final FocusNode _messageFocusNode = FocusNode();
|
|
|
|
List<dynamic>? _userInfo;
|
|
DateTime? _lastNotificationTime;
|
|
|
|
@override
|
|
void initState() {
|
|
super.initState();
|
|
|
|
chatBloc = Injector.appInstance.get<ChatBloc>();
|
|
|
|
chatBloc.add(LoadChatEvent(
|
|
serviceId: widget.service.id!,
|
|
userId: widget.service.userId,
|
|
professionalId: widget.service.professionalId,
|
|
));
|
|
|
|
_getUserAndProfessionalInfo(widget.service).then((userInfo) {
|
|
setState(() {
|
|
_userInfo = userInfo;
|
|
});
|
|
});
|
|
}
|
|
|
|
@override
|
|
void dispose() {
|
|
_messageController.dispose();
|
|
_messageFocusNode.dispose();
|
|
super.dispose();
|
|
}
|
|
|
|
@override
|
|
Widget build(BuildContext context) {
|
|
return Scaffold(
|
|
appBar: AppBar(
|
|
title: const Text('Chat'),
|
|
),
|
|
body: BlocProvider(
|
|
create: (context) => chatBloc,
|
|
child: BlocBuilder<ChatBloc, ChatState>(
|
|
builder: (context, state) {
|
|
if (state is ChatLoaded) {
|
|
return Column(
|
|
children: [
|
|
FutureBuilder(
|
|
future: _userInfo != null ? Future.value(_userInfo) : null,
|
|
builder: (BuildContext context,
|
|
AsyncSnapshot<List<dynamic>?> snapshot) {
|
|
if (_userInfo != null) {
|
|
final userInfo = _userInfo![0] as MyUser;
|
|
return Container(
|
|
padding: const EdgeInsets.symmetric(vertical: 8),
|
|
color: const Color(0xFFD6F4FF),
|
|
alignment: Alignment.topCenter,
|
|
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: widget.service.userId ==
|
|
FirebaseAuth.instance.currentUser!.uid
|
|
? GeneralReputation(
|
|
userId: widget.service.professionalId,
|
|
builder: (context, reputation) {
|
|
final average = reputation.averagePro;
|
|
final total = reputation.totalPro;
|
|
|
|
return 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()})',
|
|
),
|
|
],
|
|
);
|
|
})
|
|
: GeneralReputation(
|
|
userId: widget.service.userId,
|
|
builder: (context, reputation) {
|
|
final average = reputation.average;
|
|
final total = reputation.total;
|
|
|
|
return 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()})',
|
|
),
|
|
],
|
|
);
|
|
},
|
|
),
|
|
),
|
|
);
|
|
} else {
|
|
return const Center(child: CircularProgressIndicator());
|
|
}
|
|
},
|
|
),
|
|
Expanded(
|
|
child: SingleChildScrollView(
|
|
padding: const EdgeInsets.only(top: 10),
|
|
reverse: true,
|
|
child: Column(
|
|
children: _messagesList(state.chat.messages),
|
|
),
|
|
),
|
|
),
|
|
Container(
|
|
alignment: Alignment.bottomCenter,
|
|
child: Container(
|
|
padding: const EdgeInsets.symmetric(
|
|
horizontal: 15, vertical: 15),
|
|
child: Row(
|
|
children: [
|
|
Expanded(
|
|
child: TextFormField(
|
|
focusNode: _messageFocusNode,
|
|
controller: _messageController,
|
|
style: const TextStyle(color: Colors.black),
|
|
decoration: InputDecoration(
|
|
hintText: 'Mensaje',
|
|
hintStyle: TextStyle(
|
|
color: Colors.grey[600], fontSize: 16),
|
|
border: OutlineInputBorder(
|
|
borderSide: const BorderSide(
|
|
color: Colors.grey, width: 1.0),
|
|
borderRadius: BorderRadius.circular(50)),
|
|
focusedBorder: OutlineInputBorder(
|
|
borderSide: const BorderSide(
|
|
color: Colors.grey, width: 1.0),
|
|
borderRadius: BorderRadius.circular(50)),
|
|
contentPadding: const EdgeInsets.symmetric(
|
|
horizontal: 20, vertical: 15),
|
|
filled: true,
|
|
fillColor: Colors.grey[200],
|
|
),
|
|
onFieldSubmitted: (value) async {
|
|
if (_messageController.text.isEmpty) {
|
|
return;
|
|
}
|
|
|
|
MessageEntity message = MessageEntity(
|
|
ownerId:
|
|
FirebaseAuth.instance.currentUser!.uid,
|
|
content: _messageController.text.trim(),
|
|
createdAt: DateTime.now(),
|
|
);
|
|
|
|
chatBloc.add(SendMessageEvent(
|
|
serviceId: widget.service.id!,
|
|
message: message,
|
|
));
|
|
|
|
if (_userInfo != null) {
|
|
final userInfo = _userInfo![0] as MyUser;
|
|
|
|
if (userInfo.token == null) return;
|
|
|
|
if (_lastNotificationTime == null ||
|
|
DateTime.now().difference(
|
|
_lastNotificationTime!) >
|
|
const Duration(minutes: 5)) {
|
|
LocalNotifications.sendPushNotification(
|
|
userInfo.token!,
|
|
'Nuevo mensaje',
|
|
'Tienes un nuevo mensaje de ${userInfo.name}',
|
|
);
|
|
|
|
_lastNotificationTime = DateTime.now();
|
|
}
|
|
}
|
|
|
|
_messageController.clear();
|
|
_messageFocusNode.requestFocus();
|
|
},
|
|
),
|
|
),
|
|
const SizedBox(width: 12),
|
|
GestureDetector(
|
|
onTap: () async {
|
|
if (_messageController.text.isEmpty) {
|
|
return;
|
|
}
|
|
|
|
MessageEntity message = MessageEntity(
|
|
ownerId: FirebaseAuth.instance.currentUser!.uid,
|
|
content: _messageController.text.trim(),
|
|
createdAt: DateTime.now(),
|
|
);
|
|
|
|
chatBloc.add(SendMessageEvent(
|
|
serviceId: widget.service.id!,
|
|
message: message,
|
|
));
|
|
|
|
if (_userInfo != null) {
|
|
final userInfo = _userInfo![0] as MyUser;
|
|
|
|
if (userInfo.token == null) return;
|
|
|
|
if (_lastNotificationTime == null ||
|
|
DateTime.now().difference(
|
|
_lastNotificationTime!) >
|
|
const Duration(minutes: 5)) {
|
|
LocalNotifications.sendPushNotification(
|
|
userInfo.token!,
|
|
'Nuevo mensaje',
|
|
'Tienes un nuevo mensaje de ${userInfo.name}',
|
|
);
|
|
|
|
_lastNotificationTime = DateTime.now();
|
|
}
|
|
}
|
|
|
|
_messageController.clear();
|
|
_messageFocusNode.requestFocus();
|
|
},
|
|
child: Container(
|
|
height: 50,
|
|
width: 50,
|
|
decoration: BoxDecoration(
|
|
color: Theme.of(context).primaryColor,
|
|
borderRadius: BorderRadius.circular(30),
|
|
),
|
|
child: const Center(
|
|
child: Icon(
|
|
Icons.send,
|
|
color: Colors.white,
|
|
),
|
|
),
|
|
),
|
|
)
|
|
],
|
|
),
|
|
),
|
|
)
|
|
],
|
|
);
|
|
}
|
|
|
|
return const Center(
|
|
child: CircularProgressIndicator(),
|
|
);
|
|
},
|
|
),
|
|
),
|
|
);
|
|
}
|
|
|
|
List<Widget> _messagesList(List<MessageEntity> messages) {
|
|
return messages
|
|
.map(
|
|
(e) => e.ownerId != FirebaseAuth.instance.currentUser!.uid
|
|
? ListTile(
|
|
title: Column(
|
|
mainAxisAlignment: MainAxisAlignment.start,
|
|
crossAxisAlignment: CrossAxisAlignment.start,
|
|
children: [
|
|
Container(
|
|
margin: const EdgeInsets.only(right: 60),
|
|
padding: const EdgeInsets.symmetric(
|
|
vertical: 10, horizontal: 16),
|
|
decoration: BoxDecoration(
|
|
color: Colors.grey.shade200,
|
|
borderRadius: const BorderRadius.only(
|
|
topRight: Radius.circular(20),
|
|
bottomLeft: Radius.circular(20),
|
|
bottomRight: Radius.circular(20),
|
|
),
|
|
),
|
|
child: Text(
|
|
e.content,
|
|
style: const TextStyle(fontSize: 16),
|
|
),
|
|
),
|
|
const SizedBox(width: 5),
|
|
Padding(
|
|
padding: const EdgeInsets.symmetric(horizontal: 10),
|
|
child: Text(
|
|
DateFormat('h:mm a').format(e.createdAt),
|
|
style:
|
|
const TextStyle(color: Colors.grey, fontSize: 12),
|
|
),
|
|
),
|
|
],
|
|
),
|
|
)
|
|
: ListTile(
|
|
title: Column(
|
|
mainAxisAlignment: MainAxisAlignment.end,
|
|
crossAxisAlignment: CrossAxisAlignment.end,
|
|
children: [
|
|
Container(
|
|
margin: const EdgeInsets.only(left: 60),
|
|
padding: const EdgeInsets.symmetric(
|
|
vertical: 10,
|
|
horizontal: 16,
|
|
),
|
|
decoration: const BoxDecoration(
|
|
color: Color(0xFFD5EFFF),
|
|
borderRadius: BorderRadius.only(
|
|
topLeft: Radius.circular(20),
|
|
bottomLeft: Radius.circular(20),
|
|
bottomRight: Radius.circular(20),
|
|
),
|
|
),
|
|
child: Text(
|
|
e.content,
|
|
style: const TextStyle(fontSize: 16),
|
|
),
|
|
),
|
|
const SizedBox(width: 5),
|
|
Padding(
|
|
padding: const EdgeInsets.symmetric(horizontal: 10),
|
|
child: Text(
|
|
DateFormat('h:mm a').format(e.createdAt),
|
|
style: const TextStyle(
|
|
color: Colors.grey,
|
|
fontSize: 12,
|
|
),
|
|
),
|
|
),
|
|
],
|
|
),
|
|
),
|
|
)
|
|
.toList();
|
|
}
|
|
|
|
Future<List<dynamic>> _getUserAndProfessionalInfo(
|
|
ServiceEntity service) async {
|
|
final userRepo = FirebaseUserRepository(FirebaseAuth.instance);
|
|
|
|
final MyUser? userInfo;
|
|
|
|
if (FirebaseAuth.instance.currentUser!.uid == service.userId) {
|
|
userInfo = await userRepo.getMyUser(service.professionalId);
|
|
} else {
|
|
userInfo = await userRepo.getMyUser(service.userId);
|
|
}
|
|
|
|
return [userInfo];
|
|
}
|
|
|
|
double calculoRating(double average) {
|
|
String numeroString = average.toString();
|
|
List<String> 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;
|
|
}
|
|
}
|