From 786267d0ebb337ad5b4f1e528fdd4c23731e0606 Mon Sep 17 00:00:00 2001 From: Iván Ávalos Date: Mon, 22 May 2023 00:09:43 -0600 Subject: Se implementa funcionalidad básica de chat MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- lib/firebase/auth.dart | 16 +++++- lib/firebase/database.dart | 74 ++++++++++++++++++++++++ lib/models/chat.dart | 26 --------- lib/models/favorito.dart | 16 ------ lib/models/group.dart | 46 +++++++++++++++ lib/models/mensaje.dart | 30 ---------- lib/models/message.dart | 29 ++++++++++ lib/models/user.dart | 25 ++++++++ lib/routes.dart | 6 +- lib/screens/chat_screen.dart | 97 +++++++++++++++++++++++++++++++ lib/screens/dashboard_screen.dart | 6 +- lib/screens/new_chat_screen.dart | 99 ++++++++++++++++++++++++++++++++ lib/widgets/active_chats.dart | 42 ++++++++++++++ lib/widgets/chat_bottom_sheet.dart | 52 +++++++++++++++++ lib/widgets/chat_bubble.dart | 83 +++++++++++++++++++++++++++ lib/widgets/chat_item.dart | 113 +++++++++++++++++++++++++++++++++++++ lib/widgets/recent_chats.dart | 87 ++++++++++++++++++++++++++++ pubspec.lock | 16 ++++++ pubspec.yaml | 2 + 19 files changed, 788 insertions(+), 77 deletions(-) create mode 100644 lib/firebase/database.dart delete mode 100644 lib/models/chat.dart delete mode 100644 lib/models/favorito.dart create mode 100644 lib/models/group.dart delete mode 100644 lib/models/mensaje.dart create mode 100644 lib/models/message.dart create mode 100644 lib/models/user.dart create mode 100644 lib/screens/chat_screen.dart create mode 100644 lib/screens/new_chat_screen.dart create mode 100644 lib/widgets/active_chats.dart create mode 100644 lib/widgets/chat_bottom_sheet.dart create mode 100644 lib/widgets/chat_bubble.dart create mode 100644 lib/widgets/chat_item.dart create mode 100644 lib/widgets/recent_chats.dart diff --git a/lib/firebase/auth.dart b/lib/firebase/auth.dart index a876d5b..303412e 100644 --- a/lib/firebase/auth.dart +++ b/lib/firebase/auth.dart @@ -2,7 +2,9 @@ import 'dart:io'; import 'package:firebase_auth/firebase_auth.dart'; import 'package:flutter/foundation.dart'; +import 'package:linkchat/firebase/database.dart'; +import '../models/user.dart'; import 'storage.dart'; class Auth { @@ -10,6 +12,8 @@ class Auth { final GithubAuthProvider _githubProvider = GithubAuthProvider(); final GoogleAuthProvider _googleAuthProvider = GoogleAuthProvider(); + final Database _db = Database(); + User? get currentUser => _auth.currentUser; Future createUserWithEmailAndPassword({ @@ -25,8 +29,16 @@ class Auth { ); User? user = cred.user; if (user != null) { - user.updateDisplayName(displayName); - user.updatePhotoURL(await Storage().uploadAvatar(user.uid, avatar)); + String photoUrl = await Storage().uploadAvatar(user.uid, avatar); + await user.updateDisplayName(displayName); + await user.updatePhotoURL(photoUrl); + // Store user in database + _db.saveUser(FsUser( + uid: user.uid, + displayName: displayName, + photoUrl: photoUrl, + email: user.email!, + )); } return true; } catch (e) { diff --git a/lib/firebase/database.dart b/lib/firebase/database.dart new file mode 100644 index 0000000..eb9ef05 --- /dev/null +++ b/lib/firebase/database.dart @@ -0,0 +1,74 @@ +import 'dart:async'; + +import 'package:cloud_firestore/cloud_firestore.dart'; +import 'package:linkchat/models/group.dart'; +import 'package:linkchat/models/message.dart'; + +import '../models/user.dart'; + +class Database { + final FirebaseFirestore _firestore = FirebaseFirestore.instance; + + Future getUserById(String uid) async { + var snap = await _firestore.collection('users').doc(uid).get(); + return snap.data() != null ? FsUser.fromMap(snap.data()!) : null; + } + + Stream> getAllUsers() { + return _firestore.collection('users').snapshots().map>((e) { + return e.docs.map((e) { + return FsUser.fromMap(e.data()); + }).toList(); + }); + } + + Future saveUser(FsUser user) async { + await _firestore.collection('users').doc(user.uid).set({ + "uid": user.uid, + "displayName": user.displayName, + "photoUrl": user.photoUrl, + "email": user.email, + }); + } + + Stream> getGroupsByUserID(String uid) { + return _firestore + .collection('groups') + .where('members', arrayContains: uid) + .snapshots() + .map>((e) { + return e.docs.map((e) { + return Group.fromMap(e.data(), e.id); + }).toList(); + }); + } + + Stream> getMessagesByGroupId(String id) { + return _firestore + .collection('messages') + .doc(id) + .collection('messages') + .orderBy('sentAt') + .snapshots() + .map>((e) { + return e.docs.map((e) { + return Message.fromMap(e.data()); + }).toList(); + }); + } + + Future saveMessage(Message msg, String groupId) async { + await _firestore + .collection('messages') + .doc(groupId) + .collection('messages') + .add(msg.toMap()); + await _firestore.collection('groups').doc(groupId).update({ + "recentMessage": msg.toMap(), + }); + } + + Future saveGroup(Group group) async { + await _firestore.collection('groups').add(group.toMap()); + } +} diff --git a/lib/models/chat.dart b/lib/models/chat.dart deleted file mode 100644 index 6bd4752..0000000 --- a/lib/models/chat.dart +++ /dev/null @@ -1,26 +0,0 @@ -import 'package:linkchat/models/mensaje.dart'; - -class Chat { - final String id; - final String idUsuario1; - final String idUsuario2; - final List mensajes; - - const Chat({ - required this.id, - required this.idUsuario1, - required this.idUsuario2, - required this.mensajes, - }); - - factory Chat.fromMap(Map map) { - return Chat( - id: map['id'], - idUsuario1: map['usuario1_id'], - idUsuario2: map['usuario2_id'], - mensajes: (map['mensajes'] as List>) - .map((msj) => Mensaje.fromMap(msj)) - .toList(), - ); - } -} diff --git a/lib/models/favorito.dart b/lib/models/favorito.dart deleted file mode 100644 index 64422be..0000000 --- a/lib/models/favorito.dart +++ /dev/null @@ -1,16 +0,0 @@ -class Favorito { - final String chatId; - final String mensajeId; - - const Favorito({ - required this.chatId, - required this.mensajeId, - }); - - factory Favorito.fromMap(Map map) { - return Favorito( - chatId: map['chat_id'], - mensajeId: map['mensaje_id'], - ); - } -} diff --git a/lib/models/group.dart b/lib/models/group.dart new file mode 100644 index 0000000..dedb103 --- /dev/null +++ b/lib/models/group.dart @@ -0,0 +1,46 @@ +import 'package:cloud_firestore/cloud_firestore.dart'; + +import 'message.dart'; + +class Group { + final String? id; + final String? name; + final Message? recentMessage; + final List members; + final String createdBy; + final DateTime createdAt; + + const Group({ + this.id, + this.name, + this.recentMessage, + this.members = const [], + required this.createdBy, + required this.createdAt, + }); + + Map toMap() { + return { + "id": id, + "name": name, + "recentMessage": recentMessage, + "members": members, + "createdBy": createdBy, + "createdAt": createdAt, + }; + } + + factory Group.fromMap(Map map, String id) { + List members = map['members']; + return Group( + id: id, + name: map['name'], + recentMessage: map['recentMessage'] != null + ? Message.fromMap(map['recentMessage']) + : null, + members: members.map((m) => m.toString()).toList(), + createdBy: map['createdBy'], + createdAt: (map['createdAt'] as Timestamp).toDate(), + ); + } +} diff --git a/lib/models/mensaje.dart b/lib/models/mensaje.dart deleted file mode 100644 index c2f4354..0000000 --- a/lib/models/mensaje.dart +++ /dev/null @@ -1,30 +0,0 @@ -enum Direccion { a2b, b2a } - -class Mensaje { - final String id; - final String link; - final String? titulo; - final String? imagen; - final String fecha; - final Direccion direccion; - - const Mensaje({ - required this.id, - required this.link, - this.titulo, - this.imagen, - required this.fecha, - required this.direccion, - }); - - factory Mensaje.fromMap(Map map) { - return Mensaje( - id: map['id'], - link: map['link'], - titulo: map.containsKey('titulo') ? map['titulo'] : null, - imagen: map.containsKey('imagen') ? map['imagen'] : null, - fecha: map['fecha'], - direccion: (map['fecha'] as int) == 0 ? Direccion.a2b : Direccion.b2a, - ); - } -} diff --git a/lib/models/message.dart b/lib/models/message.dart new file mode 100644 index 0000000..ac31c18 --- /dev/null +++ b/lib/models/message.dart @@ -0,0 +1,29 @@ +import 'package:cloud_firestore/cloud_firestore.dart'; + +class Message { + final String messageText; + final DateTime sentAt; + final String sentBy; + + const Message({ + required this.messageText, + required this.sentAt, + required this.sentBy, + }); + + Map toMap() { + return { + "messageText": messageText, + "sentAt": sentAt, + "sentBy": sentBy, + }; + } + + factory Message.fromMap(Map map) { + return Message( + messageText: map['messageText'], + sentAt: (map['sentAt'] as Timestamp).toDate(), + sentBy: map['sentBy'], + ); + } +} diff --git a/lib/models/user.dart b/lib/models/user.dart new file mode 100644 index 0000000..1827552 --- /dev/null +++ b/lib/models/user.dart @@ -0,0 +1,25 @@ +class FsUser { + final String uid; + final String displayName; + final String photoUrl; + final String email; + final List groups; + + const FsUser({ + required this.uid, + required this.displayName, + required this.photoUrl, + required this.email, + this.groups = const [], + }); + + factory FsUser.fromMap(Map map) { + return FsUser( + uid: map['uid'], + displayName: map['displayName'], + photoUrl: map['photoUrl'], + email: map['email'], + groups: map['groups'] ?? [], + ); + } +} diff --git a/lib/routes.dart b/lib/routes.dart index 9446a01..a2ad16e 100644 --- a/lib/routes.dart +++ b/lib/routes.dart @@ -1,4 +1,6 @@ import 'package:flutter/material.dart'; +import 'package:linkchat/screens/chat_screen.dart'; +import 'package:linkchat/screens/new_chat_screen.dart'; import 'screens/dashboard_screen.dart'; import 'screens/login_screen.dart'; @@ -10,6 +12,8 @@ Map getApplicationRoutes() { '/login': (BuildContext context) => const LoginScreen(), '/register': (BuildContext context) => const RegisterScreen(), '/onboard': (BuildContext context) => const OnboardingScreen(), - '/dash': (BuildContext context) => const DashboardScreen() + '/dash': (BuildContext context) => const DashboardScreen(), + '/new': (BuildContext context) => const NewChatScreen(), + '/chat': (BuildContext context) => const ChatScreen(), }; } diff --git a/lib/screens/chat_screen.dart b/lib/screens/chat_screen.dart new file mode 100644 index 0000000..d67e469 --- /dev/null +++ b/lib/screens/chat_screen.dart @@ -0,0 +1,97 @@ +import 'package:flutter/material.dart'; +import 'package:linkchat/firebase/auth.dart'; +import 'package:linkchat/firebase/database.dart'; +import 'package:linkchat/models/group.dart'; + +import '../models/message.dart'; +import '../models/user.dart'; +import '../widgets/chat_bottom_sheet.dart'; +import '../widgets/chat_bubble.dart'; + +class ChatScreen extends StatefulWidget { + const ChatScreen({super.key}); + + @override + State createState() => _ChatScreenState(); +} + +class _ChatScreenState extends State { + bool init = false; + Group? group; + FsUser? user; + final Auth _auth = Auth(); + final Database _db = Database(); + + @override + Widget build(BuildContext context) { + List arguments = + ModalRoute.of(context)?.settings.arguments as List; + group = arguments[0] as Group; + user = arguments[1] as FsUser; + + return Scaffold( + appBar: PreferredSize( + preferredSize: const Size.fromHeight(70.0), + child: Padding( + padding: const EdgeInsets.only(top: 5), + child: AppBar( + leadingWidth: 30, + title: Row( + children: [ + ClipRRect( + borderRadius: BorderRadius.circular(30), + child: CircleAvatar( + backgroundImage: NetworkImage(user!.photoUrl), + )), + Padding( + padding: const EdgeInsets.only(left: 10), + child: Text(user!.displayName), + ), + ], + ), + actions: const [ + Padding( + padding: EdgeInsets.only(right: 10), + child: Icon( + Icons.more_vert, + ), + ) + ], + ), + ), + ), + body: StreamBuilder( + stream: _db.getMessagesByGroupId(group!.id!), + builder: (context, snapshot) { + if (snapshot.hasData) { + List msgs = snapshot.data!; + return ListView.builder( + itemCount: snapshot.data!.length, + itemBuilder: (context, index) => ChatBubble( + msgs[index].messageText, + alignment: msgs[index].sentBy == _auth.currentUser?.uid + ? ChatBubbleAlignment.end + : ChatBubbleAlignment.start, + ), + ); + } else if (snapshot.hasError) { + print('Error: ${snapshot.error}'); + return const Center(child: Text('Hubo un error')); + } + return const Center(child: CircularProgressIndicator()); + }, + ), + bottomSheet: ChatBottomSheet( + onPressed: (value) { + _db.saveMessage( + Message( + messageText: value, + sentBy: _auth.currentUser!.uid, + sentAt: DateTime.now(), + ), + group!.id!); + }, + ), + ); + } +} diff --git a/lib/screens/dashboard_screen.dart b/lib/screens/dashboard_screen.dart index cb31a58..778c2bb 100644 --- a/lib/screens/dashboard_screen.dart +++ b/lib/screens/dashboard_screen.dart @@ -4,6 +4,7 @@ import 'package:provider/provider.dart'; import '../firebase/auth.dart'; import '../providers/theme_provider.dart'; import '../settings/themes.dart'; +import '../widgets/recent_chats.dart'; class DashboardScreen extends StatefulWidget { const DashboardScreen({super.key}); @@ -30,8 +31,9 @@ class _DashboardScreenState extends State { SliverAppBar.large( title: const Text('Inicio'), ), - const SliverFillRemaining( - child: Placeholder(), + SliverFillRemaining( + hasScrollBody: true, + child: RecentChats(), ), ], ), diff --git a/lib/screens/new_chat_screen.dart b/lib/screens/new_chat_screen.dart new file mode 100644 index 0000000..7b7721f --- /dev/null +++ b/lib/screens/new_chat_screen.dart @@ -0,0 +1,99 @@ +import 'package:flutter/material.dart'; +import 'package:linkchat/firebase/database.dart'; +import 'package:linkchat/models/group.dart'; + +import '../firebase/auth.dart'; +import '../models/user.dart'; + +class NewChatScreen extends StatefulWidget { + const NewChatScreen({super.key}); + + @override + State createState() => _NewChatScreenState(); +} + +class _NewChatScreenState extends State { + final Auth _auth = Auth(); + final Database _db = Database(); + final TextEditingController _controller = TextEditingController(); + List users = []; + List filteredUsers = []; + + @override + void initState() { + super.initState(); + _db.getAllUsers().first.then((u) { + setState(() { + users = u; + filteredUsers = u; + }); + }); + } + + @override + Widget build(BuildContext context) { + return Scaffold( + appBar: AppBar( + title: const Text('Nuevo chat'), + leading: IconButton( + icon: const Icon(Icons.arrow_back), + onPressed: () { + Navigator.of(context).pop(); + }, + ), + ), + body: Column( + children: [ + TextField( + controller: _controller, + decoration: const InputDecoration( + border: OutlineInputBorder(), + labelText: 'Nombre del contacto', + ), + onChanged: (value) { + setState(() { + if (value.isNotEmpty) { + filteredUsers = users + .where((user) => user.displayName.contains(value)) + .toList(); + } else { + filteredUsers = users; + } + }); + }, + ), + ListView.builder( + shrinkWrap: true, + itemCount: filteredUsers.length, + itemBuilder: (context, index) { + FsUser user = filteredUsers[index]; + return ListTile( + leading: CircleAvatar( + backgroundImage: NetworkImage(user.photoUrl), + ), + title: Text(user.displayName), + trailing: IconButton( + icon: const Icon(Icons.send), + onPressed: () { + _db + .saveGroup(Group( + createdBy: _auth.currentUser!.uid, + createdAt: DateTime.now(), + members: [ + _auth.currentUser!.uid, + user.uid, + ], + )) + .whenComplete(() { + Navigator.of(context).pop(); + }); + }, + ), + ); + }, + ), + ], + ), + ); + } +} diff --git a/lib/widgets/active_chats.dart b/lib/widgets/active_chats.dart new file mode 100644 index 0000000..6893edb --- /dev/null +++ b/lib/widgets/active_chats.dart @@ -0,0 +1,42 @@ +import 'package:flutter/material.dart'; + +class ActiveChats extends StatelessWidget { + @override + Widget build(BuildContext context) { + return Padding( + padding: const EdgeInsets.only(top: 25, left: 5), + child: SingleChildScrollView( + scrollDirection: Axis.horizontal, + child: Row( + children: [ + for (int i = 0; i < 10; i++) + Padding( + padding: + const EdgeInsets.symmetric(vertical: 10, horizontal: 12), + child: Container( + width: 55, + height: 55, + decoration: BoxDecoration( + color: Colors.white, + borderRadius: BorderRadius.circular(35), + boxShadow: [ + BoxShadow( + color: Colors.grey.withOpacity(0.5), + blurRadius: 10, + spreadRadius: 2, + offset: const Offset(0, 3), + ), + ]), + child: const ClipRRect( + //child: Image.asset( + // "images/avatar.png", + //), + child: Icon(Icons.person), + ), + ), + ), + ], + )), + ); + } +} diff --git a/lib/widgets/chat_bottom_sheet.dart b/lib/widgets/chat_bottom_sheet.dart new file mode 100644 index 0000000..f1124cf --- /dev/null +++ b/lib/widgets/chat_bottom_sheet.dart @@ -0,0 +1,52 @@ +import 'package:flutter/material.dart'; + +class ChatBottomSheet extends StatelessWidget { + final Function(String msg) onPressed; + final TextEditingController _controller = TextEditingController(); + + ChatBottomSheet({super.key, required this.onPressed}); + + @override + Widget build(BuildContext context) { + return Container( + height: 55, + decoration: BoxDecoration(boxShadow: [ + BoxShadow( + color: Colors.grey.withOpacity(0.5), + spreadRadius: 2, + blurRadius: 10, + offset: const Offset(0, 3), + ), + ]), + child: Row( + children: [ + Padding( + padding: const EdgeInsets.only(left: 10), + child: Container( + alignment: Alignment.centerRight, + width: 270, + child: TextFormField( + controller: _controller, + decoration: const InputDecoration( + hintText: "Escribe algo", + border: InputBorder.none, + ), + ), + ), + ), + const Spacer(), + Padding( + padding: const EdgeInsets.only(right: 10), + child: IconButton( + icon: const Icon(Icons.send), + onPressed: () { + onPressed(_controller.value.text); + _controller.clear(); + }, + ), + ), + ], + ), + ); + } +} diff --git a/lib/widgets/chat_bubble.dart b/lib/widgets/chat_bubble.dart new file mode 100644 index 0000000..aaac93d --- /dev/null +++ b/lib/widgets/chat_bubble.dart @@ -0,0 +1,83 @@ +import 'package:custom_clippers/custom_clippers.dart'; +import 'package:flutter/material.dart'; + +enum ChatBubbleAlignment { start, end } + +class ChatBubble extends StatelessWidget { + final String text; + final ChatBubbleAlignment alignment; + + const ChatBubble( + this.text, { + super.key, + required this.alignment, + }); + + @override + Widget build(BuildContext context) { + return Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + alignment == ChatBubbleAlignment.start + ? BubbleLeft(text) + : BubbleRight(text), + ], + ); + } +} + +class BubbleLeft extends StatelessWidget { + final String text; + + const BubbleLeft(this.text, {super.key}); + + @override + Widget build(BuildContext context) { + return Padding( + padding: const EdgeInsets.only(right: 77), + child: ClipPath( + clipper: UpperNipMessageClipper(MessageType.receive), + child: Container( + padding: const EdgeInsets.all(20), + decoration: const BoxDecoration( + color: Color(0xFFE1E1E2), + ), + child: Text( + text, + style: const TextStyle(fontSize: 15, color: Colors.black), + ), + ), + ), + ); + } +} + +class BubbleRight extends StatelessWidget { + final String text; + + const BubbleRight(this.text, {super.key}); + + @override + Widget build(BuildContext context) { + return Container( + alignment: Alignment.centerRight, + child: Padding( + padding: const EdgeInsets.only(top: 20, left: 77), + child: ClipPath( + clipper: LowerNipMessageClipper(MessageType.send), + child: Container( + padding: + const EdgeInsets.only(left: 20, top: 10, bottom: 20, right: 20), + decoration: const BoxDecoration( + color: Color(0xFF113753), + ), + child: Text( + text, + style: const TextStyle(fontSize: 15, color: Colors.white), + ), + ), + ), + ), + ); + } +} diff --git a/lib/widgets/chat_item.dart b/lib/widgets/chat_item.dart new file mode 100644 index 0000000..d04c607 --- /dev/null +++ b/lib/widgets/chat_item.dart @@ -0,0 +1,113 @@ +import 'package:flutter/material.dart'; +import 'package:intl/intl.dart'; + +class ChatItem extends StatelessWidget { + final String title; + final String subtitle; + final DateTime timestamp; + final int? unread; + final String? avatarURL; + final Function() onTap; + + const ChatItem({ + super.key, + required this.title, + required this.subtitle, + required this.onTap, + required this.timestamp, + this.unread = 0, + this.avatarURL, + }); + + @override + Widget build(BuildContext context) { + return Padding( + padding: const EdgeInsets.symmetric(vertical: 15), + child: InkWell( + onTap: onTap, + child: Row( + children: [ + ClipRRect( + borderRadius: BorderRadius.circular(35), + child: CircleAvatar( + backgroundColor: Theme.of(context).colorScheme.primary, + backgroundImage: + avatarURL != null ? NetworkImage(avatarURL!) : null, + child: avatarURL == null + ? Icon( + Icons.person, + color: Theme.of(context).colorScheme.onPrimary, + ) + : null, + )), + Expanded( + flex: 12, + child: Padding( + padding: const EdgeInsets.symmetric(horizontal: 20), + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Text( + title, + style: const TextStyle( + fontSize: 17, + color: Colors.black, + fontWeight: FontWeight.bold, + ), + overflow: TextOverflow.ellipsis, + ), + const SizedBox(height: 10), + Text( + subtitle, + style: const TextStyle( + fontSize: 17, + color: Colors.black54, + ), + overflow: TextOverflow.ellipsis, + ), + ], + ), + ), + ), + const Spacer(), + Padding( + padding: const EdgeInsets.only(right: 10), + child: Column( + crossAxisAlignment: CrossAxisAlignment.center, + children: [ + Text( + DateFormat('kk:mm').format(timestamp), + style: const TextStyle( + fontSize: 15, + color: Colors.black54, + ), + ), + const SizedBox(height: 10), + unread != null && unread != 0 + ? Container( + height: 23, + width: 23, + alignment: Alignment.center, + decoration: BoxDecoration( + color: const Color(0xFF113753), + borderRadius: BorderRadius.circular(25), + ), + child: Text( + unread!.toString(), + style: const TextStyle( + color: Colors.white, + fontSize: 17, + fontWeight: FontWeight.bold, + ), + ), + ) + : const SizedBox.shrink(), + ], + ), + ) + ], + ), + ), + ); + } +} diff --git a/lib/widgets/recent_chats.dart b/lib/widgets/recent_chats.dart new file mode 100644 index 0000000..02d4959 --- /dev/null +++ b/lib/widgets/recent_chats.dart @@ -0,0 +1,87 @@ +import 'package:flutter/material.dart'; +import 'package:linkchat/firebase/database.dart'; + +import '../firebase/auth.dart'; +import '../models/group.dart'; +import '../widgets/chat_item.dart'; + +class RecentChats extends StatelessWidget { + RecentChats({super.key}); + + final Auth _auth = Auth(); + final Database _db = Database(); + + @override + Widget build(BuildContext context) { + return Container( + margin: const EdgeInsets.only(top: 20), + padding: const EdgeInsets.symmetric(horizontal: 10, vertical: 25), + decoration: BoxDecoration( + color: Colors.white, + borderRadius: const BorderRadius.only( + topLeft: Radius.circular(35), + topRight: Radius.circular(35), + ), + boxShadow: [ + BoxShadow( + color: Colors.grey.withOpacity(0.5), + blurRadius: 10, + spreadRadius: 2, + offset: const Offset(0, 2), + ), + ]), + child: StreamBuilder( + stream: _db.getGroupsByUserID(_auth.currentUser!.uid), + builder: (context, snapshot) { + if (snapshot.hasData) { + return ListView.builder( + itemCount: snapshot.data!.length, + itemBuilder: (context, index) { + Group group = snapshot.data![index]; + print("Members: ${group.members}"); + print("User ID: ${_auth.currentUser!.uid}"); + return FutureBuilder( + future: _db.getUserById(group.members.firstWhere( + (m) => m != _auth.currentUser!.uid, + orElse: () => group.createdBy)), + builder: (context, snapshot) { + if (snapshot.hasData) { + return ChatItem( + title: snapshot.data!.displayName, + subtitle: group.recentMessage?.messageText ?? "", + timestamp: + group.recentMessage?.sentAt ?? DateTime.now(), + avatarURL: snapshot.data!.photoUrl, + onTap: () { + Navigator.of(context).pushNamed( + '/chat', + arguments: [group, snapshot.data!], + ); + }, + ); + } else if (snapshot.hasError) { + return ChatItem( + title: "Usuario desconocido", + subtitle: group.recentMessage?.messageText ?? "", + timestamp: DateTime.now(), + onTap: () { + Navigator.of(context).pushNamed( + '/chat', + arguments: [group, snapshot.data!], + ); + }, + ); + } + return const Center(child: CircularProgressIndicator()); + }); + }, + ); + } else if (snapshot.hasError) { + print("Error: ${snapshot.error}"); + return const Center(child: Text('Hubo un error')); + } + return const Center(child: CircularProgressIndicator()); + }), + ); + } +} diff --git a/pubspec.lock b/pubspec.lock index 28080bd..9039956 100644 --- a/pubspec.lock +++ b/pubspec.lock @@ -113,6 +113,14 @@ packages: url: "https://pub.dev" source: hosted version: "3.0.3" + custom_clippers: + dependency: "direct main" + description: + name: custom_clippers + sha256: "2b83bb29ccbbd7d2d39220ec0361becb8ab545c3a0cb295979e3450bdad6034b" + url: "https://pub.dev" + source: hosted + version: "2.0.0" email_validator: dependency: "direct main" description: @@ -328,6 +336,14 @@ packages: url: "https://pub.dev" source: hosted version: "2.6.3" + intl: + dependency: "direct main" + description: + name: intl + sha256: "3bc132a9dbce73a7e4a21a17d06e1878839ffbf975568bc875c60537824b0c4d" + url: "https://pub.dev" + source: hosted + version: "0.18.1" js: dependency: transitive description: diff --git a/pubspec.yaml b/pubspec.yaml index 90b0bf8..03c2aa2 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -23,6 +23,8 @@ dependencies: firebase_messaging: ^14.6.1 firebase_storage: ^11.2.1 cloud_firestore: ^4.7.1 + custom_clippers: ^2.0.0 + intl: ^0.18.1 dev_dependencies: flutter_test: -- cgit v1.2.3