2024-04-30 19:25:16 +02:00
|
|
|
import 'package:cloud_firestore/cloud_firestore.dart';
|
|
|
|
import 'package:flutter/material.dart';
|
|
|
|
|
2024-05-26 01:44:49 +02:00
|
|
|
import '../components/chat_bubble.dart';
|
|
|
|
import '../components/my_textfield.dart';
|
2024-06-09 03:01:52 +02:00
|
|
|
import '../constants.dart';
|
2024-05-26 01:44:49 +02:00
|
|
|
import '../services/auth/auth_service.dart';
|
|
|
|
import '../services/chat/chat_service.dart';
|
|
|
|
|
2024-04-30 19:25:16 +02:00
|
|
|
class ChatPage extends StatefulWidget {
|
|
|
|
final String receiverEmail;
|
|
|
|
final String receiverID;
|
2024-06-09 03:01:52 +02:00
|
|
|
final String chatTitle;
|
2024-04-30 19:25:16 +02:00
|
|
|
|
|
|
|
const ChatPage({
|
|
|
|
super.key,
|
|
|
|
required this.receiverEmail,
|
|
|
|
required this.receiverID,
|
2024-06-09 03:01:52 +02:00
|
|
|
required this.chatTitle,
|
2024-04-30 19:25:16 +02:00
|
|
|
});
|
|
|
|
|
|
|
|
@override
|
|
|
|
State<ChatPage> createState() => _ChatPageState();
|
|
|
|
}
|
|
|
|
|
|
|
|
class _ChatPageState extends State<ChatPage> {
|
|
|
|
// 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(
|
2024-06-09 03:01:52 +02:00
|
|
|
appBar: AppBar(title: Text(widget.chatTitle)),
|
2024-04-30 19:25:16 +02:00
|
|
|
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<String, dynamic> data = doc.data() as Map<String, dynamic>;
|
|
|
|
|
|
|
|
// align message to the right if sender is current user, otherwise left
|
2024-06-09 03:01:52 +02:00
|
|
|
bool isCurrentUser = data[Constants.dbFieldMessageSenderId] ==
|
|
|
|
_authService.getCurrentUser()!.uid;
|
2024-04-30 19:25:16 +02:00
|
|
|
var alignment =
|
|
|
|
isCurrentUser ? Alignment.centerRight : Alignment.centerLeft;
|
|
|
|
|
2024-05-03 11:12:36 +02:00
|
|
|
List<String> msgDate =
|
2024-06-09 03:01:52 +02:00
|
|
|
(data[Constants.dbFieldMessageTimestamp] as Timestamp)
|
|
|
|
.toDate()
|
|
|
|
.toIso8601String()
|
|
|
|
.split("T");
|
2024-05-03 11:12:36 +02:00
|
|
|
|
2024-04-30 19:25:16 +02:00
|
|
|
return Container(
|
|
|
|
alignment: alignment,
|
|
|
|
child: Column(
|
|
|
|
crossAxisAlignment:
|
|
|
|
isCurrentUser ? CrossAxisAlignment.end : CrossAxisAlignment.start,
|
|
|
|
children: [
|
2024-05-03 11:12:36 +02:00
|
|
|
Padding(
|
|
|
|
padding: const EdgeInsets.symmetric(horizontal: 25.0),
|
|
|
|
child: Text(
|
2024-06-14 14:15:11 +02:00
|
|
|
"${msgDate[0]} ${msgDate[1].substring(0, 5)}",
|
|
|
|
style: const TextStyle(color: Colors.grey),
|
2024-05-03 11:12:36 +02:00
|
|
|
),
|
|
|
|
),
|
2024-04-30 19:25:16 +02:00
|
|
|
ChatBubble(
|
2024-06-09 03:01:52 +02:00
|
|
|
message: data[Constants.dbFieldMessageText],
|
2024-04-30 19:25:16 +02:00
|
|
|
isCurrentUser: isCurrentUser,
|
|
|
|
),
|
|
|
|
],
|
|
|
|
),
|
|
|
|
);
|
|
|
|
}
|
|
|
|
|
|
|
|
Widget _buildUserInput() {
|
|
|
|
return Padding(
|
2024-04-30 22:33:01 +02:00
|
|
|
padding: const EdgeInsets.only(bottom: 10.0),
|
2024-04-30 19:25:16 +02:00
|
|
|
child: Row(
|
|
|
|
children: [
|
|
|
|
// text should take up most of space
|
|
|
|
Expanded(
|
|
|
|
child: MyTextField(
|
|
|
|
controller: _messageController,
|
2024-05-26 01:44:49 +02:00
|
|
|
hintText: 'Type a message',
|
|
|
|
obscureText: false,
|
2024-04-30 19:25:16 +02:00
|
|
|
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),
|
2024-04-30 22:33:01 +02:00
|
|
|
color: Colors.white,
|
2024-04-30 19:25:16 +02:00
|
|
|
),
|
|
|
|
)
|
|
|
|
],
|
|
|
|
),
|
|
|
|
);
|
|
|
|
}
|
|
|
|
}
|