import 'package:cloud_firestore/cloud_firestore.dart'; import 'package:cofounderella/components/chat_bubble.dart'; import 'package:cofounderella/components/my_textfield.dart'; import 'package:cofounderella/services/auth/auth_service.dart'; import 'package:cofounderella/services/chat/chat_service.dart'; import 'package:flutter/material.dart'; class ChatPage extends StatefulWidget { final String receiverEmail; final String receiverID; const ChatPage({ super.key, required this.receiverEmail, required this.receiverID, }); @override State createState() => _ChatPageState(); } class _ChatPageState extends State { // text controller final TextEditingController _messageController = TextEditingController(); // chat and auth services final ChatService _chatService = ChatService(); final AuthService _authService = AuthService(); // for textfield focus FocusNode myFocusNode = FocusNode(); @override void initState() { super.initState(); // add listener to focus node myFocusNode.addListener(() { if (myFocusNode.hasFocus) { // cause a delay so that the keyboard has time to show up // then the amount of remaining space will be calculated // then scroll down Future.delayed( const Duration(milliseconds: 500), () => scrollDown(), ); } }); // wait a bit for listview to be built, then scroll to bottom Future.delayed( const Duration(milliseconds: 500), () => scrollDown(), ); } @override void dispose() { myFocusNode.dispose(); _messageController.dispose(); super.dispose(); } // scroll controller final ScrollController _scrollController = ScrollController(); void scrollDown() { _scrollController.animateTo( _scrollController.position.maxScrollExtent, duration: const Duration(seconds: 1), curve: Curves.fastOutSlowIn, ); } void sendMessage() async { // send message if not empty if (_messageController.text.isNotEmpty) { await _chatService.sendMessage( widget.receiverID, _messageController.text); // clear text controller _messageController.clear(); } scrollDown(); } @override Widget build(BuildContext context) { return Scaffold( appBar: AppBar(title: Text(widget.receiverEmail)), body: Column( children: [ // display all messages Expanded( child: _buildMessageList(), ), // user input _buildUserInput(), ], ), ); } Widget _buildMessageList() { String senderID = _authService.getCurrentUser()!.uid; return StreamBuilder( stream: _chatService.getMessages(senderID, widget.receiverID), builder: (context, snapshot) { // errors if (snapshot.hasError) { return Text("Error: ${snapshot.error}"); } // loading if (snapshot.connectionState == ConnectionState.waiting) { return const Text("Loading.."); } // return list view return ListView( controller: _scrollController, children: snapshot.data!.docs.map((doc) => _buildMessageItem(doc)).toList(), ); }, ); } Widget _buildMessageItem(DocumentSnapshot doc) { Map data = doc.data() as Map; // align message to the right if sender is current user, otherwise left bool isCurrentUser = data['senderID'] == _authService.getCurrentUser()!.uid; var alignment = isCurrentUser ? Alignment.centerRight : Alignment.centerLeft; return Container( alignment: alignment, child: Column( crossAxisAlignment: isCurrentUser ? CrossAxisAlignment.end : CrossAxisAlignment.start, children: [ ChatBubble( message: data["message"], isCurrentUser: isCurrentUser, ), ], ), ); } Widget _buildUserInput() { return Padding( padding: const EdgeInsets.only(bottom: 50.0), child: Row( children: [ // text should take up most of space Expanded( child: MyTextField( controller: _messageController, hintText: "Type a message", obscureText: false, // TODO make this optional focusNode: myFocusNode, ), ), // send button Container( decoration: const BoxDecoration( color: Colors.green, shape: BoxShape.circle, ), margin: const EdgeInsets.only(right: 25), child: IconButton( onPressed: sendMessage, icon: const Icon(Icons.send), color: Colors.white, // TODO check colors ), ) ], ), ); } }