diff options
author | Iván Ávalos <avalos@disroot.org> | 2023-05-22 23:06:18 -0600 |
---|---|---|
committer | Iván Ávalos <avalos@disroot.org> | 2023-05-22 23:06:18 -0600 |
commit | 79bdf9cac504d35cf3cf671232b28b5b5ac03f12 (patch) | |
tree | c8ddb66955468f399a8a368464d0db53dc5c8f8d | |
parent | 9139a65003d758b4ee52758149b178d8dc990e0e (diff) | |
download | linkchat-79bdf9cac504d35cf3cf671232b28b5b5ac03f12.tar.gz linkchat-79bdf9cac504d35cf3cf671232b28b5b5ac03f12.tar.bz2 linkchat-79bdf9cac504d35cf3cf671232b28b5b5ac03f12.zip |
Se implementan favoritos
-rw-r--r-- | lib/firebase/database.dart | 63 | ||||
-rw-r--r-- | lib/models/favorite.dart | 63 | ||||
-rw-r--r-- | lib/models/group.dart | 2 | ||||
-rw-r--r-- | lib/models/message.dart | 5 | ||||
-rw-r--r-- | lib/routes.dart | 2 | ||||
-rw-r--r-- | lib/screens/chat_screen.dart | 42 | ||||
-rw-r--r-- | lib/screens/dashboard_screen.dart | 8 | ||||
-rw-r--r-- | lib/screens/favorites_screen.dart | 86 | ||||
-rw-r--r-- | lib/screens/new_chat_screen.dart | 94 | ||||
-rw-r--r-- | lib/widgets/active_chats.dart | 42 | ||||
-rw-r--r-- | lib/widgets/chat_bubble.dart | 46 |
11 files changed, 351 insertions, 102 deletions
diff --git a/lib/firebase/database.dart b/lib/firebase/database.dart index 46970ce..3203748 100644 --- a/lib/firebase/database.dart +++ b/lib/firebase/database.dart @@ -1,6 +1,7 @@ import 'dart:async'; import 'package:cloud_firestore/cloud_firestore.dart'; +import 'package:linkchat/models/favorite.dart'; import 'package:linkchat/models/group.dart'; import 'package:linkchat/models/message.dart'; import 'package:simple_link_preview/simple_link_preview.dart'; @@ -10,6 +11,10 @@ import '../models/user.dart'; class Database { final FirebaseFirestore _firestore = FirebaseFirestore.instance; + // + // USERS + // + Future<FsUser?> getUserById(String uid) async { var snap = await _firestore.collection('users').doc(uid).get(); return snap.data() != null ? FsUser.fromMap(snap.data()!) : null; @@ -32,6 +37,10 @@ class Database { }); } + // + // GROUPS + // + Stream<List<Group>> getGroupsByUserID(String uid) { return _firestore .collection('groups') @@ -44,6 +53,14 @@ class Database { }); } + Future<void> saveGroup(Group group) async { + await _firestore.collection('groups').add(group.toMap()); + } + + // + // MESSAGES + // + Stream<List<Message>> getMessagesByGroupId(String id) { return _firestore .collection('messages') @@ -53,7 +70,7 @@ class Database { .snapshots() .map<List<Message>>((e) { return e.docs.map((e) { - return Message.fromMap(e.data()); + return Message.fromMap(e.data(), e.id); }).toList(); }); } @@ -70,7 +87,47 @@ class Database { }); } - Future<void> saveGroup(Group group) async { - await _firestore.collection('groups').add(group.toMap()); + // + // FAVORITES + // + + Stream<List<Favorite>> getFavoritesByUserID(String uid) { + return _firestore + .collection('favorites') + .doc(uid) + .collection('favorites') + .orderBy('savedAt', descending: true) + .snapshots() + .map<List<Favorite>>( + (e) => e.docs.map((e) => Favorite.fromMap(e.data())).toList(), + ); + } + + Stream<bool> hasFavoriteForMessage(String userId, String? messageId) { + return _firestore + .collection('favorites') + .doc(userId) + .collection('favorites') + .where('messageId', isEqualTo: messageId) + .snapshots() + .map<bool>((e) => e.docs.isNotEmpty); + } + + Future<void> saveFavorite(Favorite favorite, String userId) { + return _firestore + .collection('favorites') + .doc(userId) + .collection('favorites') + .add(favorite.toMap()); + } + + Future<void> removeFavorite(String userId, String? messageId) async { + CollectionReference reference = + _firestore.collection('favorites').doc(userId).collection('favorites'); + var favorites = + await reference.where('messageId', isEqualTo: messageId).get(); + for (var e in favorites.docs) { + reference.doc(e.id).delete(); + } } } diff --git a/lib/models/favorite.dart b/lib/models/favorite.dart new file mode 100644 index 0000000..5e11b68 --- /dev/null +++ b/lib/models/favorite.dart @@ -0,0 +1,63 @@ +import 'package:cloud_firestore/cloud_firestore.dart'; + +import 'message.dart'; + +class Favorite { + final String groupId; + final String messageId; + final DateTime savedAt; + + final String messageText; + final String? linkTitle; + final String? linkDescription; + final String? linkPhotoURL; + final DateTime sentAt; + final String sentBy; + + const Favorite({ + required this.groupId, + required this.messageId, + required this.savedAt, + required this.messageText, + required this.linkTitle, + required this.linkDescription, + required this.linkPhotoURL, + required this.sentAt, + required this.sentBy, + }); + + Map<String, dynamic> toMap() => { + "groupId": groupId, + "messageId": messageId, + "savedAt": savedAt, + "messageText": messageText, + "linkTitle": linkTitle, + "linkDescription": linkDescription, + "linkPhotoURL": linkPhotoURL, + "sentAt": sentAt, + "sentBy": sentBy, + }; + + Message getMessage() => Message( + messageText: messageText, + linkTitle: linkTitle, + linkDescription: linkDescription, + linkPhotoURL: linkPhotoURL, + sentAt: sentAt, + sentBy: sentBy, + ); + + factory Favorite.fromMap(Map<String, dynamic> map) { + return Favorite( + groupId: map['groupId'], + messageId: map['messageId'], + savedAt: (map['savedAt'] as Timestamp).toDate(), + messageText: map['messageText'], + linkTitle: map['linkTitle'], + linkDescription: map['linkDescription'], + linkPhotoURL: map['linkPhotoURL'], + sentAt: (map['sentAt'] as Timestamp).toDate(), + sentBy: map['sentBy'], + ); + } +} diff --git a/lib/models/group.dart b/lib/models/group.dart index dedb103..7995004 100644 --- a/lib/models/group.dart +++ b/lib/models/group.dart @@ -36,7 +36,7 @@ class Group { id: id, name: map['name'], recentMessage: map['recentMessage'] != null - ? Message.fromMap(map['recentMessage']) + ? Message.fromMap(map['recentMessage'], null) : null, members: members.map((m) => m.toString()).toList(), createdBy: map['createdBy'], diff --git a/lib/models/message.dart b/lib/models/message.dart index 4026215..03a85c5 100644 --- a/lib/models/message.dart +++ b/lib/models/message.dart @@ -2,6 +2,7 @@ import 'package:cloud_firestore/cloud_firestore.dart'; import 'package:simple_link_preview/simple_link_preview.dart'; class Message { + final String? id; final String messageText; final String? linkTitle; final String? linkDescription; @@ -10,6 +11,7 @@ class Message { final String sentBy; const Message({ + this.id, required this.messageText, this.linkTitle, this.linkDescription, @@ -29,8 +31,9 @@ class Message { }; } - factory Message.fromMap(Map<String, dynamic> map) { + factory Message.fromMap(Map<String, dynamic> map, String? id) { return Message( + id: id, messageText: map['messageText'], linkTitle: map['linkTitle'], linkDescription: map['linkDescription'], diff --git a/lib/routes.dart b/lib/routes.dart index a2ad16e..ae7ac12 100644 --- a/lib/routes.dart +++ b/lib/routes.dart @@ -1,5 +1,6 @@ import 'package:flutter/material.dart'; import 'package:linkchat/screens/chat_screen.dart'; +import 'package:linkchat/screens/favorites_screen.dart'; import 'package:linkchat/screens/new_chat_screen.dart'; import 'screens/dashboard_screen.dart'; @@ -15,5 +16,6 @@ Map<String, WidgetBuilder> getApplicationRoutes() { '/dash': (BuildContext context) => const DashboardScreen(), '/new': (BuildContext context) => const NewChatScreen(), '/chat': (BuildContext context) => const ChatScreen(), + '/favorites': (BuildContext context) => const FavoritesScreen(), }; } diff --git a/lib/screens/chat_screen.dart b/lib/screens/chat_screen.dart index bd0d5d7..61f3f9e 100644 --- a/lib/screens/chat_screen.dart +++ b/lib/screens/chat_screen.dart @@ -1,8 +1,10 @@ +import 'package:firebase_auth/firebase_auth.dart'; 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/favorite.dart'; import '../models/message.dart'; import '../models/user.dart'; import '../widgets/chat_bottom_sheet.dart'; @@ -75,16 +77,44 @@ class _ChatScreenState extends State<ChatScreen> { }); List<Message> msgs = snapshot.data!; + User? user = _auth.currentUser; return ListView.builder( controller: _scroll, padding: const EdgeInsets.only(bottom: 80.0), itemCount: snapshot.data!.length, - itemBuilder: (context, index) => ChatBubble( - msgs[index], - alignment: msgs[index].sentBy == _auth.currentUser?.uid - ? ChatBubbleAlignment.end - : ChatBubbleAlignment.start, - ), + itemBuilder: (context, index) => StreamBuilder( + stream: _db.hasFavoriteForMessage( + _auth.currentUser!.uid, msgs[index].id), + builder: (context, snapshot) { + return ChatBubble( + msgs[index], + alignment: msgs[index].sentBy == user?.uid + ? ChatBubbleAlignment.end + : ChatBubbleAlignment.start, + favorited: snapshot.hasData + ? snapshot.data == true + : (snapshot.hasError ? false : false), + onFavorite: (String id, bool value) { + if (value) { + _db.saveFavorite( + Favorite( + groupId: group!.id!, + messageId: msgs[index].id!, + savedAt: DateTime.now(), + messageText: msgs[index].messageText, + linkTitle: msgs[index].linkTitle, + linkDescription: msgs[index].linkDescription, + linkPhotoURL: msgs[index].linkPhotoURL, + sentAt: msgs[index].sentAt, + sentBy: msgs[index].sentBy, + ), + user!.uid); + } else { + _db.removeFavorite(user!.uid, msgs[index].id); + } + }, + ); + }), ); } else if (snapshot.hasError) { print('Error: ${snapshot.error}'); diff --git a/lib/screens/dashboard_screen.dart b/lib/screens/dashboard_screen.dart index 88bd8e7..97a9de6 100644 --- a/lib/screens/dashboard_screen.dart +++ b/lib/screens/dashboard_screen.dart @@ -30,6 +30,14 @@ class _DashboardScreenState extends State<DashboardScreen> { slivers: [ SliverAppBar.large( title: const Text('Inicio'), + actions: [ + IconButton( + icon: const Icon(Icons.favorite_outline), + onPressed: () { + Navigator.of(context).pushNamed('/favorites'); + }, + ) + ], ), SliverFillRemaining( hasScrollBody: true, diff --git a/lib/screens/favorites_screen.dart b/lib/screens/favorites_screen.dart new file mode 100644 index 0000000..4d6f949 --- /dev/null +++ b/lib/screens/favorites_screen.dart @@ -0,0 +1,86 @@ +import 'package:flutter/material.dart'; +import 'package:linkchat/firebase/database.dart'; +import 'package:linkchat/widgets/chat_bubble.dart'; + +import '../firebase/auth.dart'; +import '../models/favorite.dart'; + +class FavoritesScreen extends StatefulWidget { + const FavoritesScreen({super.key}); + + @override + State<FavoritesScreen> createState() => _FavoritesScreenState(); +} + +class _FavoritesScreenState extends State<FavoritesScreen> { + final Auth _auth = Auth(); + final Database _db = Database(); + final TextEditingController _controller = TextEditingController(); + List<Favorite> favorites = []; + List<Favorite> filteredFavorites = []; + + @override + void initState() { + super.initState(); + _db.getFavoritesByUserID(_auth.currentUser!.uid).first.then((f) { + setState(() { + favorites = f; + filteredFavorites = f; + }); + }).onError((e, st) { + print(e); + }); + } + + @override + Widget build(BuildContext context) { + return Scaffold( + appBar: AppBar( + title: const Text('Favoritos'), + leading: IconButton( + icon: const Icon(Icons.arrow_back), + onPressed: () { + Navigator.of(context).pop(); + }, + ), + ), + body: Column( + children: [ + Padding( + padding: const EdgeInsets.all(8.0), + child: TextField( + controller: _controller, + decoration: const InputDecoration( + border: OutlineInputBorder(), + labelText: 'Buscar', + ), + onChanged: (value) { + setState(() { + if (value.isNotEmpty) { + filteredFavorites = favorites + .where((fav) => + fav.messageText.contains(value) == true || + fav.linkTitle?.contains(value) == true || + fav.linkDescription?.contains(value) == true) + .toList(); + } else { + filteredFavorites = favorites; + } + }); + }, + ), + ), + Expanded( + child: ListView.builder( + itemCount: filteredFavorites.length, + itemBuilder: (context, index) { + Favorite fav = filteredFavorites[index]; + return LinkPreview(fav.getMessage()); + }, + ), + ), + ], + ), + ); + } +} diff --git a/lib/screens/new_chat_screen.dart b/lib/screens/new_chat_screen.dart index 7b7721f..bf7486f 100644 --- a/lib/screens/new_chat_screen.dart +++ b/lib/screens/new_chat_screen.dart @@ -44,53 +44,57 @@ class _NewChatScreenState extends State<NewChatScreen> { ), body: Column( children: [ - TextField( - controller: _controller, - decoration: const InputDecoration( - border: OutlineInputBorder(), - labelText: 'Nombre del contacto', + Padding( + padding: const EdgeInsets.all(8.0), + child: 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; + } + }); + }, ), - 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(); - }); - }, - ), - ); - }, + Expanded( + child: ListView.builder( + 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 deleted file mode 100644 index 6893edb..0000000 --- a/lib/widgets/active_chats.dart +++ /dev/null @@ -1,42 +0,0 @@ -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_bubble.dart b/lib/widgets/chat_bubble.dart index ba90f6b..e7622e4 100644 --- a/lib/widgets/chat_bubble.dart +++ b/lib/widgets/chat_bubble.dart @@ -10,11 +10,15 @@ enum ChatBubbleAlignment { start, end } class ChatBubble extends StatelessWidget {
final Message message;
final ChatBubbleAlignment alignment;
+ final bool favorited;
+ final Function(String id, bool value) onFavorite;
const ChatBubble(
this.message, {
super.key,
required this.alignment,
+ this.favorited = false,
+ required this.onFavorite,
});
@override
@@ -23,8 +27,16 @@ class ChatBubble extends StatelessWidget { crossAxisAlignment: CrossAxisAlignment.start,
children: [
alignment == ChatBubbleAlignment.start
- ? BubbleLeft(message)
- : BubbleRight(message),
+ ? BubbleLeft(
+ message,
+ favorited: favorited,
+ onFavorite: onFavorite,
+ )
+ : BubbleRight(
+ message,
+ favorited: favorited,
+ onFavorite: onFavorite,
+ ),
],
);
}
@@ -32,8 +44,15 @@ class ChatBubble extends StatelessWidget { class BubbleLeft extends StatelessWidget {
final Message message;
+ final bool favorited;
+ final Function(String id, bool value) onFavorite;
- const BubbleLeft(this.message, {super.key});
+ const BubbleLeft(
+ this.message, {
+ super.key,
+ this.favorited = false,
+ required this.onFavorite,
+ });
@override
Widget build(BuildContext context) {
@@ -60,6 +79,12 @@ class BubbleLeft extends StatelessWidget { ),
),
),
+ IconButton(
+ icon: favorited
+ ? const Icon(Icons.favorite, color: Colors.black)
+ : const Icon(Icons.favorite_outline, color: Colors.black),
+ onPressed: () => onFavorite(message.id!, !favorited),
+ ),
],
),
),
@@ -70,8 +95,15 @@ class BubbleLeft extends StatelessWidget { class BubbleRight extends StatelessWidget {
final Message message;
+ final bool favorited;
+ final Function(String id, bool value) onFavorite;
- const BubbleRight(this.message, {super.key});
+ const BubbleRight(
+ this.message, {
+ super.key,
+ this.favorited = false,
+ required this.onFavorite,
+ });
@override
Widget build(BuildContext context) {
@@ -101,6 +133,12 @@ class BubbleRight extends StatelessWidget { ),
),
),
+ IconButton(
+ icon: favorited
+ ? const Icon(Icons.favorite, color: Colors.white)
+ : const Icon(Icons.favorite_outline, color: Colors.white),
+ onPressed: () => onFavorite(message.id!, !favorited),
+ ),
],
),
),
|