replace: swap prosappweb content for prosapp_web_app (more complete version)
prosapp_web_app has chat, dashboard, calendar, support, 13 providers and Fluro URL routing. Keep Dockerfile + nginx.conf from previous prosappweb. Upgrade google_fonts 6.2.1 → 8.1.0 (Dart 3.12 compat fix). Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
co-authored by
Claude Sonnet 4.6
parent
74a4f41902
commit
15175c1b91
@@ -0,0 +1,350 @@
|
||||
import 'package:firebase_auth/firebase_auth.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter_rating_bar/flutter_rating_bar.dart';
|
||||
import 'package:intl/intl.dart';
|
||||
import 'package:prosapp_web_app/models/message_entity.dart';
|
||||
import 'package:prosapp_web_app/providers/chat_provider.dart';
|
||||
import 'package:prosapp_web_app/providers/score_provider.dart';
|
||||
import 'package:prosapp_web_app/providers/services_provider.dart';
|
||||
import 'package:prosapp_web_app/providers/sidemenu_provider.dart';
|
||||
import 'package:prosapp_web_app/services/navigation_service.dart';
|
||||
import 'package:provider/provider.dart';
|
||||
|
||||
class ChatView extends StatelessWidget {
|
||||
final String type;
|
||||
final String serviceId;
|
||||
final String professionalId;
|
||||
|
||||
const ChatView({
|
||||
super.key,
|
||||
required this.type,
|
||||
required this.serviceId,
|
||||
required this.professionalId,
|
||||
});
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
final chatProvider = Provider.of<ChatProvider>(context);
|
||||
final servicesProvider =
|
||||
Provider.of<ServicesProvider>(context, listen: false);
|
||||
final scoreProvider = Provider.of<ScoreProvider>(context, listen: false);
|
||||
|
||||
scoreProvider.loadReputation(professionalId);
|
||||
|
||||
return StreamBuilder(
|
||||
stream: chatProvider.getChat(serviceId),
|
||||
builder: (context, snapshot) {
|
||||
if (!snapshot.hasData) {
|
||||
return const Center(child: CircularProgressIndicator());
|
||||
}
|
||||
|
||||
final chat = snapshot.data;
|
||||
|
||||
if (chat == null) {
|
||||
return const Center(
|
||||
child: Text('Chat no encontrado'),
|
||||
);
|
||||
}
|
||||
|
||||
final service = servicesProvider.service?.service;
|
||||
final user = servicesProvider.service?.user;
|
||||
final reputation = scoreProvider.reputation;
|
||||
|
||||
if (service == null || user == null) {
|
||||
return const Center(child: CircularProgressIndicator());
|
||||
}
|
||||
|
||||
final image = (user.picture == '' || user.picture == null)
|
||||
? const Image(image: AssetImage('no-image.jpg'))
|
||||
: FadeInImage.assetNetwork(
|
||||
placeholder: 'loader.gif',
|
||||
fit: BoxFit.cover,
|
||||
image: user.picture!,
|
||||
);
|
||||
|
||||
return Center(
|
||||
child: ConstrainedBox(
|
||||
constraints: const BoxConstraints(maxWidth: 800),
|
||||
child: Column(
|
||||
children: [
|
||||
Container(
|
||||
padding: const EdgeInsets.symmetric(vertical: 15),
|
||||
color: const Color(0xFFD6F4FF),
|
||||
alignment: Alignment.topCenter,
|
||||
child: Row(
|
||||
children: [
|
||||
const SizedBox(width: 20),
|
||||
SizedBox(
|
||||
width: 85,
|
||||
height: 85,
|
||||
child: ClipOval(child: image),
|
||||
),
|
||||
const SizedBox(width: 20),
|
||||
Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
Text(
|
||||
user.name,
|
||||
overflow: TextOverflow.ellipsis,
|
||||
style: const TextStyle(
|
||||
fontSize: 18,
|
||||
fontWeight: FontWeight.bold,
|
||||
),
|
||||
),
|
||||
Row(
|
||||
children: [
|
||||
RatingBar.builder(
|
||||
initialRating: type == 'user'
|
||||
? calculoRating(reputation.averagePro)
|
||||
: calculoRating(reputation.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),
|
||||
if (type == 'user')
|
||||
Text(
|
||||
'${reputation.averagePro.toStringAsFixed(1)} (${reputation.totalPro.toString()})',
|
||||
),
|
||||
if (type == 'professional')
|
||||
Text(
|
||||
'${reputation.average.toStringAsFixed(1)} (${reputation.total.toString()})',
|
||||
),
|
||||
],
|
||||
)
|
||||
],
|
||||
)
|
||||
],
|
||||
),
|
||||
),
|
||||
Expanded(
|
||||
child: SingleChildScrollView(
|
||||
padding: const EdgeInsets.only(top: 10),
|
||||
reverse: true,
|
||||
child: Column(
|
||||
children: _messagesList(chat.messages),
|
||||
),
|
||||
),
|
||||
),
|
||||
_MessageInput(
|
||||
serviceId: serviceId,
|
||||
userId: FirebaseAuth.instance.currentUser!.uid),
|
||||
],
|
||||
),
|
||||
),
|
||||
);
|
||||
},
|
||||
);
|
||||
}
|
||||
|
||||
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;
|
||||
}
|
||||
|
||||
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.shade300,
|
||||
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(
|
||||
_getFechaHoraFormateada(e.createdAt),
|
||||
style:
|
||||
const TextStyle(color: Colors.grey, fontSize: 11),
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
)
|
||||
: 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(
|
||||
_getFechaHoraFormateada(e.createdAt),
|
||||
style: const TextStyle(
|
||||
color: Colors.grey,
|
||||
fontSize: 11,
|
||||
),
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
)
|
||||
.toList();
|
||||
}
|
||||
|
||||
String _getFechaHoraFormateada(DateTime fecha) {
|
||||
final now = DateTime.now();
|
||||
final yesterday = now.subtract(const Duration(days: 1));
|
||||
final formatter = DateFormat('h:mm a');
|
||||
|
||||
if (fecha.day == now.day &&
|
||||
fecha.month == now.month &&
|
||||
fecha.year == now.year) {
|
||||
return 'Hoy - ${formatter.format(fecha)}';
|
||||
} else if (fecha.day == yesterday.day &&
|
||||
fecha.month == yesterday.month &&
|
||||
fecha.year == yesterday.year) {
|
||||
return 'Ayer - ${formatter.format(fecha)}';
|
||||
} else {
|
||||
return '${DateFormat('dd/MM/yyyy').format(fecha)} - ${formatter.format(fecha)}';
|
||||
}
|
||||
}
|
||||
|
||||
void navigateTo(String routeName) {
|
||||
NavigationService.replaceTo(routeName);
|
||||
SideMenuProvider.closeMenu();
|
||||
}
|
||||
}
|
||||
|
||||
class _MessageInput extends StatefulWidget {
|
||||
final String serviceId;
|
||||
final String userId;
|
||||
|
||||
const _MessageInput({
|
||||
required this.serviceId,
|
||||
required this.userId,
|
||||
});
|
||||
|
||||
@override
|
||||
State<_MessageInput> createState() => _MessageInputState();
|
||||
}
|
||||
|
||||
class _MessageInputState extends State<_MessageInput> {
|
||||
final _controller = TextEditingController();
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
final chatProvider = Provider.of<ChatProvider>(context, listen: false);
|
||||
|
||||
void sendMessage() {
|
||||
if (_controller.text.trim().isNotEmpty) {
|
||||
final message = MessageEntity(
|
||||
ownerId: widget.userId,
|
||||
content: _controller.text.trim(),
|
||||
createdAt: DateTime.now(),
|
||||
);
|
||||
chatProvider.sendMessage(widget.serviceId, message);
|
||||
_controller.clear();
|
||||
}
|
||||
}
|
||||
|
||||
return Padding(
|
||||
padding: const EdgeInsets.all(8.0),
|
||||
child: Row(
|
||||
children: [
|
||||
Expanded(
|
||||
child: TextField(
|
||||
controller: _controller,
|
||||
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],
|
||||
),
|
||||
onSubmitted: (value) => sendMessage(),
|
||||
),
|
||||
),
|
||||
const SizedBox(width: 12),
|
||||
GestureDetector(
|
||||
onTap: () => sendMessage(),
|
||||
child: Container(
|
||||
height: 50,
|
||||
width: 50,
|
||||
decoration: BoxDecoration(
|
||||
color: const Color(0xFF2BA4EC),
|
||||
borderRadius: BorderRadius.circular(30),
|
||||
),
|
||||
child: const Center(
|
||||
child: Icon(
|
||||
Icons.send,
|
||||
color: Colors.white,
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user