aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorIván Ávalos <avalos@disroot.org>2023-05-22 23:06:18 -0600
committerIván Ávalos <avalos@disroot.org>2023-05-22 23:06:18 -0600
commit79bdf9cac504d35cf3cf671232b28b5b5ac03f12 (patch)
treec8ddb66955468f399a8a368464d0db53dc5c8f8d
parent9139a65003d758b4ee52758149b178d8dc990e0e (diff)
downloadlinkchat-79bdf9cac504d35cf3cf671232b28b5b5ac03f12.tar.gz
linkchat-79bdf9cac504d35cf3cf671232b28b5b5ac03f12.tar.bz2
linkchat-79bdf9cac504d35cf3cf671232b28b5b5ac03f12.zip
Se implementan favoritos
-rw-r--r--lib/firebase/database.dart63
-rw-r--r--lib/models/favorite.dart63
-rw-r--r--lib/models/group.dart2
-rw-r--r--lib/models/message.dart5
-rw-r--r--lib/routes.dart2
-rw-r--r--lib/screens/chat_screen.dart42
-rw-r--r--lib/screens/dashboard_screen.dart8
-rw-r--r--lib/screens/favorites_screen.dart86
-rw-r--r--lib/screens/new_chat_screen.dart94
-rw-r--r--lib/widgets/active_chats.dart42
-rw-r--r--lib/widgets/chat_bubble.dart46
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),
+ ),
],
),
),