From f0e45a5a510d2b4693580179645bd0bb0b352f86 Mon Sep 17 00:00:00 2001 From: Iván Ávalos Date: Mon, 22 May 2023 09:00:45 -0600 Subject: Soporte y restricción para links MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- lib/firebase/database.dart | 6 ++- lib/models/message.dart | 15 +++++- lib/screens/chat_screen.dart | 26 ++++++--- lib/widgets/chat_bottom_sheet.dart | 10 +--- lib/widgets/chat_bubble.dart | 105 ++++++++++++++++++++++++++++++++----- lib/widgets/recent_chats.dart | 3 +- 6 files changed, 130 insertions(+), 35 deletions(-) (limited to 'lib') diff --git a/lib/firebase/database.dart b/lib/firebase/database.dart index eb9ef05..46970ce 100644 --- a/lib/firebase/database.dart +++ b/lib/firebase/database.dart @@ -3,6 +3,7 @@ import 'dart:async'; import 'package:cloud_firestore/cloud_firestore.dart'; import 'package:linkchat/models/group.dart'; import 'package:linkchat/models/message.dart'; +import 'package:simple_link_preview/simple_link_preview.dart'; import '../models/user.dart'; @@ -58,13 +59,14 @@ class Database { } Future saveMessage(Message msg, String groupId) async { + LinkPreview? preview = await SimpleLinkPreview.getPreview(msg.messageText); await _firestore .collection('messages') .doc(groupId) .collection('messages') - .add(msg.toMap()); + .add(msg.toMap(preview: preview)); await _firestore.collection('groups').doc(groupId).update({ - "recentMessage": msg.toMap(), + "recentMessage": msg.toMap(preview: preview), }); } diff --git a/lib/models/message.dart b/lib/models/message.dart index ac31c18..4026215 100644 --- a/lib/models/message.dart +++ b/lib/models/message.dart @@ -1,19 +1,29 @@ import 'package:cloud_firestore/cloud_firestore.dart'; +import 'package:simple_link_preview/simple_link_preview.dart'; class Message { final String messageText; + final String? linkTitle; + final String? linkDescription; + final String? linkPhotoURL; final DateTime sentAt; final String sentBy; const Message({ required this.messageText, + this.linkTitle, + this.linkDescription, + this.linkPhotoURL, required this.sentAt, required this.sentBy, }); - Map toMap() { + Map toMap({LinkPreview? preview}) { return { "messageText": messageText, + "linkTitle": preview?.title, + "linkDescription": preview?.description, + "linkPhotoURL": preview?.image, "sentAt": sentAt, "sentBy": sentBy, }; @@ -22,6 +32,9 @@ class Message { factory Message.fromMap(Map map) { return Message( 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/screens/chat_screen.dart b/lib/screens/chat_screen.dart index d67e469..d3d71ad 100644 --- a/lib/screens/chat_screen.dart +++ b/lib/screens/chat_screen.dart @@ -66,9 +66,10 @@ class _ChatScreenState extends State { if (snapshot.hasData) { List msgs = snapshot.data!; return ListView.builder( + padding: const EdgeInsets.only(bottom: 80.0), itemCount: snapshot.data!.length, itemBuilder: (context, index) => ChatBubble( - msgs[index].messageText, + msgs[index], alignment: msgs[index].sentBy == _auth.currentUser?.uid ? ChatBubbleAlignment.end : ChatBubbleAlignment.start, @@ -83,13 +84,24 @@ class _ChatScreenState extends State { ), bottomSheet: ChatBottomSheet( onPressed: (value) { - _db.saveMessage( - Message( - messageText: value, - sentBy: _auth.currentUser!.uid, - sentAt: DateTime.now(), + if (value.startsWith('https://') && Uri.tryParse(value) != null) { + _db.saveMessage( + Message( + messageText: value, + sentBy: _auth.currentUser!.uid, + sentAt: DateTime.now(), + ), + group!.id!); + } else { + ScaffoldMessenger.of(context).showSnackBar( + const SnackBar( + // behavior: SnackBarBehavior.floating, + dismissDirection: DismissDirection.none, + content: Text('El mensaje debe ser un link.'), + duration: Duration(seconds: 1), ), - group!.id!); + ); + } }, ), ); diff --git a/lib/widgets/chat_bottom_sheet.dart b/lib/widgets/chat_bottom_sheet.dart index f1124cf..ac08499 100644 --- a/lib/widgets/chat_bottom_sheet.dart +++ b/lib/widgets/chat_bottom_sheet.dart @@ -8,16 +8,8 @@ class ChatBottomSheet extends StatelessWidget { @override Widget build(BuildContext context) { - return Container( + return SizedBox( 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( diff --git a/lib/widgets/chat_bubble.dart b/lib/widgets/chat_bubble.dart index aaac93d..7c2e16c 100644 --- a/lib/widgets/chat_bubble.dart +++ b/lib/widgets/chat_bubble.dart @@ -1,14 +1,16 @@ import 'package:custom_clippers/custom_clippers.dart'; import 'package:flutter/material.dart'; +import 'package:linkchat/models/message.dart'; +import 'package:url_launcher/link.dart'; enum ChatBubbleAlignment { start, end } class ChatBubble extends StatelessWidget { - final String text; + final Message message; final ChatBubbleAlignment alignment; const ChatBubble( - this.text, { + this.message, { super.key, required this.alignment, }); @@ -19,17 +21,17 @@ class ChatBubble extends StatelessWidget { crossAxisAlignment: CrossAxisAlignment.start, children: [ alignment == ChatBubbleAlignment.start - ? BubbleLeft(text) - : BubbleRight(text), + ? BubbleLeft(message) + : BubbleRight(message), ], ); } } class BubbleLeft extends StatelessWidget { - final String text; + final Message message; - const BubbleLeft(this.text, {super.key}); + const BubbleLeft(this.message, {super.key}); @override Widget build(BuildContext context) { @@ -42,9 +44,20 @@ class BubbleLeft extends StatelessWidget { decoration: const BoxDecoration( color: Color(0xFFE1E1E2), ), - child: Text( - text, - style: const TextStyle(fontSize: 15, color: Colors.black), + child: Column( + children: [ + LinkPreview(message), + Link( + uri: Uri.parse(message.messageText), + builder: (context, followLink) => TextButton( + onPressed: followLink, + child: Text( + message.messageText, + style: const TextStyle(color: Colors.blue), + ), + ), + ), + ], ), ), ), @@ -53,9 +66,9 @@ class BubbleLeft extends StatelessWidget { } class BubbleRight extends StatelessWidget { - final String text; + final Message message; - const BubbleRight(this.text, {super.key}); + const BubbleRight(this.message, {super.key}); @override Widget build(BuildContext context) { @@ -71,9 +84,21 @@ class BubbleRight extends StatelessWidget { decoration: const BoxDecoration( color: Color(0xFF113753), ), - child: Text( - text, - style: const TextStyle(fontSize: 15, color: Colors.white), + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + LinkPreview(message), + Link( + uri: Uri.parse(message.messageText), + builder: (context, followLink) => TextButton( + onPressed: followLink, + child: Text( + message.messageText, + style: const TextStyle(color: Colors.blue), + ), + ), + ), + ], ), ), ), @@ -81,3 +106,55 @@ class BubbleRight extends StatelessWidget { ); } } + +class LinkPreview extends StatelessWidget { + final Message message; + const LinkPreview(this.message, {super.key}); + + @override + Widget build(BuildContext context) { + return (message.linkTitle != null || + message.linkDescription != null || + message.linkPhotoURL != null) + ? Padding( + padding: const EdgeInsets.only(bottom: 8.0), + child: Card( + clipBehavior: Clip.antiAliasWithSaveLayer, + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + message.linkPhotoURL != null + ? FadeInImage( + placeholder: const AssetImage('assets/loading.gif'), + image: NetworkImage(message.linkPhotoURL!), + imageErrorBuilder: (context, error, stackTrace) => + const SizedBox.shrink(), + fit: BoxFit.fill, + ) + : const SizedBox.shrink(), + message.linkTitle != null + ? Padding( + padding: const EdgeInsets.all(8.0), + child: Text( + message.linkTitle!, + style: Theme.of(context) + .textTheme + .bodyLarge! + .copyWith(fontWeight: FontWeight.bold), + ), + ) + : const SizedBox.shrink(), + message.linkDescription != null + ? Padding( + padding: + const EdgeInsets.fromLTRB(8.0, 0.0, 8.0, 8.0), + child: Text(message.linkDescription!), + ) + : const SizedBox.shrink(), + ], + ), + ), + ) + : const SizedBox.shrink(); + } +} diff --git a/lib/widgets/recent_chats.dart b/lib/widgets/recent_chats.dart index 02d4959..b83b5e5 100644 --- a/lib/widgets/recent_chats.dart +++ b/lib/widgets/recent_chats.dart @@ -35,11 +35,10 @@ class RecentChats extends StatelessWidget { builder: (context, snapshot) { if (snapshot.hasData) { return ListView.builder( + padding: const EdgeInsets.only(bottom: 100.0), 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, -- cgit v1.2.3