(이 코드는 게시글 작성자에게 채팅을 거는 방식으로, writer와 contact 존재)
1. 유저들은 고유 userId 존재
2. Userdata에는 상대방의 아이디와 함께 닉네임 존재
4. 채팅아이디는 (게시글 아이디 + contact유저 id)의 구조
5. 채팅은 리스트로 받음
6. 메세지는 [보낸 시간, 메세지 내용, 보낸 userId]의 구조
7. 게시글에서 채팅을 건 시점의 코드
8. 유저별로 채팅id 목록 리스트 존재
data class ChatData(
val id: String?,
val writer: UserData,
val contact: UserData,
val messages: List<MessageData>
)
data class UserData(
val id: Int?,
val nickname: String
)
data class MessageData(
val content: String,
val createdAt: Long,
val from: Int
)
var chatMessages = MutableStateFlow<List<MessageData>>(emptyList())
var chatData = MutableStateFlow<ChatData?>(null)
var userId = 29 (임시)
// 채팅방 들어가자마자 조회 - 한번도 채팅하지 않은 경우(채팅방이 생성되어있지 않은 경우) 조회불가
fun enterChatRoom(chatId: String){
// 한번도 채팅하지 않은경우는 조회 불가
Log.d("채팅방 id", chatId)
firebaseDB.reference.child("chat").child(chatId).get()
.addOnSuccessListener {
Log.d("채팅방 정보", it.value.toString())
// 데이터는 hashMap 형태로 오기때문에 객체 형태로 변환해줘야함
it.value?.let { value ->
val result = value as HashMap<String, Any>?
val writer = result?.get("writer") as HashMap<String, Any>?
val contact = result?.get("contact") as HashMap<String, Any>?
val _chatData = ChatData(
result?.get("id") as String,
UserData((writer?.get("id") as Long).toInt(), writer["nickname"] as String, (writer["level"] as Long).toInt()),
UserData((contact?.get("id") as Long).toInt(), contact["nickname"] as String, (contact["level"] as Long).toInt()),
result["messages"] as List<MessageData>
)
chatData.value = _chatData
}
}
.addOnFailureListener{
Log.d("채팅룸 정보 가져오기 실패", it.toString())
}
val chatListener = object : ValueEventListener {
override fun onDataChange(snapshot: DataSnapshot) {
val _chatMessage = arrayListOf<MessageData>()
val messageData = snapshot.value as ArrayList<HashMap<String, Any>>?
// snapshot은 hashMap 형태로 오기때문에 객체 형태로 변환해줘야함
messageData?.forEach {
_chatMessage.add(
MessageData(
it["content"] as String,
it["createdAt"] as Long,
(it["from"] as Long).toInt()
)
)
}
chatMessages.value = arrayListOf()
chatMessages.value = _chatMessage.toList()
Log.d("변화 리스너2", chatMessages.value.toString())
}
override fun onCancelled(error: DatabaseError) {
Log.d(TAG, "loadMessage:onCancelled", error.toException())
}
}
firebaseDB.reference.child("chat").child(chatId).child("messages").addValueEventListener(chatListener)
Firebase
}
// 메세지 보내기
fun newMessage(chatId: String, messageData: MessageData){
if(chatMessages.value.isEmpty()) {
chatMessages.value = listOf(messageData)
// 첫 메세지일때 채팅방 생성 - 채팅룸이 생성되는 시점
newChatRoom(chatId, userId, chatMessages.value)
}else{
chatMessages.value += messageData
firebaseDB.reference.child("chat").child(chatId).child("messages").setValue(chatMessages.value)
.addOnSuccessListener {
Log.d("newChatRoomSuccess", "메세지 보내기 성공")
}
.addOnFailureListener{
Log.d("메세지 보내기 실패", it.toString())
}
}
}
// 채팅룸 생성
private fun newChatRoom(chatId: String, postId: Int, writerId: Int, message :List<MessageData>){
val userId = UserSharedPreference(App.context()).getUserPrefs("id").toString()
var usersChatList: List<String> = emptyList()
viewModelScope.launch {
viewModelScope.async {
firebaseDB.reference.child("user").child(userId).get()
.addOnSuccessListener {
it.value?.let { it ->
usersChatList = it as List<String>
}
Log.d("유저 채팅 목록 가져옴", usersChatList.toString())
}
.addOnFailureListener {
isHave = false
}
}.await()
// db에서 user정보 가져옴
val writerData: ArrayList<UserData> = dbAccessModule.getUserInfoById(writerId)
val writer = UserData(writerData[0].id, writerData[0].nickname)
// sharedPreference에 저장된 본인 user정보
val contactData = UserSharedPreference(App.context()).getUserPrefs()
val contact = RdbUserData(contactData.id, contactData.nickname
val chatData = ChatData(chatId, writer, contact, message)
firebaseDB.reference.child("chat").child(chatId).setValue(chatData)
.addOnSuccessListener {
Log.d("newChatRoomSuccess", "채팅룸 생성 완료")
// 생성시 채팅 listener 재호출
enterChatRoom(chatId)
}
.addOnFailureListener {
Log.d("채팅룸 생성 실패", it.toString())
}
if (usersChatList.isEmpty()){
usersChatList = listOf(chatId)
}else {
usersChatList.plus(chatId)
}
Log.d("유저 채팅 리스트", usersChatList.toString())
firebaseDB.reference.child("user").child(userId).setValue(usersChatList)
.addOnSuccessListener {
Log.d("newChatRoomSuccess", "유저 정보에 추가 완료")
enterChatRoom(chatId)
}
.addOnFailureListener {
Log.d("유저 정보 추가 실패", it.toString())
}
}
val scaffoldState = rememberScaffoldState()
Scaffold(
scaffoldState = scaffoldState,
topBar = {
TopAppBar(
title = { Text(text = "", textAlign = TextAlign.Center, modifier = Modifier.fillMaxWidth(), fontSize = 17.sp) },
navigationIcon = {
IconButton(onClick = { navController.navigateUp() }) {
Icon(painterResource(id = R.drawable.icon_back), contentDescription = "뒤로가기", modifier = Modifier.size(35.dp), tint = colorResource(
id = R.color.green)
)
}
},
actions = {
IconButton(onClick = { declarationDialogState = true }) {
Icon(painterResource(id = R.drawable.icon_decl), contentDescription = "신고하기", modifier = Modifier.size(45.dp), tint = Color.Red)
}
},
backgroundColor = Color.Transparent,
elevation = 0.dp
)
}
) { it ->
Column(modifier = Modifier.padding(it), verticalArrangement = Arrangement.Bottom) {
ChatSection(chatViewModel.chatMessages.collectAsState(),chatViewModel.chatData.collectAsState(), userId, Modifier.weight(1f))
SendSection(chatViewModel, chatId, userId)
}
}
LaunchedEffect(Unit) {
// 채팅창 들어가서 정보 가져오기- 채팅 데이터, 채팅 내용
chatViewModel.enterChatRoom(chatId)
}
}
@Composable
fun ChatSection(message: State<List<MessageData>?>, chatData: State<ChatData?>, userId: Int, modifier: Modifier = Modifier) {
//userId = 내 아이디
val writerId: Int? = chatData.value?.writer?.id
// 상대방 닉네임
val nickname = if(writerId == userId){
chatData.value?.contact?.nickname
}else{
chatData.value?.writer?.nickname
}
LazyColumn(
modifier = Modifier
.padding(start = 15.dp, end = 15.dp, top = 15.dp),
verticalArrangement = Arrangement.Bottom
) {
message.value?.let {
items(it) { message ->
nickname?.let { nickname ->
MessageItem(message, userId, nickname)
Spacer(modifier = Modifier.height(13.dp))
}
}
}
}
}
@Composable
fun MessageItem(message: MessageData, userId: Int, nickname: String) {
val current = System.currentTimeMillis()
val calculateTime = CalculateTime()
// 시간 계산 - 방금, 몇분 전, 몇시 몇분
val time = calculateTime.calTimeToChat(current, message.createdAt)
// 본인일때 true
val isMe = message.from == userId
Row(
modifier = Modifier.fillMaxWidth(),
horizontalArrangement = if (message.from != userId) Arrangement.Start else Arrangement.End,
verticalAlignment = Alignment.Bottom
) {
if (isMe) {
Text(text = time, color = Color.Gray, modifier = Modifier.padding(start = 7.dp, end = 7.dp), fontSize = 13.sp)
}
Column {
if (!isMe) {
Row{
Text(text = nickname, color = Color.Black, fontSize = 13.sp, modifier = Modifier.padding(bottom = 5.dp, end = 5.dp))
Image(
painter = painterResource(R.drawable.sprout),
contentDescription = "App icon",
modifier = Modifier
.clip(shape = CircleShape)
.size(17.dp)
)
}
}
if (message.content != "") {
Box(
modifier = if (isMe) {
Modifier
.background(
color = Color.Yellow,
shape = RoundedCornerShape(10.dp, 0.dp, 10.dp, 10.dp)
)
.padding(top = 8.dp, bottom = 8.dp, start = 16.dp, end = 16.dp)
} else {
Modifier
.background(
color = Color.LightGray,
shape = RoundedCornerShape(0.dp, 10.dp, 10.dp, 10.dp)
)
.padding(top = 8.dp, bottom = 8.dp, start = 16.dp, end = 16.dp)
}
) {
Text(text = message.content, color = Color.Black)
}
}
}
if (!isMe) {
Text(text = time, color = Color.Gray, modifier = Modifier.padding(start = 7.dp), fontSize = 13.sp)
}
}
}
@Composable
fun SendSection(viewModel: ChatViewModel, chatId: String, userId: Int) {
val sendMessage = remember {
mutableStateOf("")
}
val timestamp = remember {
mutableStateOf<Long>(0)
}
Card(modifier = Modifier.fillMaxWidth()) {
OutlinedTextField(
value = sendMessage.value,
onValueChange = { sendMessage.value = it },
placeholder = { Text(text = "메세지를 작성해주세요") },
trailingIcon = {
IconButton(
onClick = {
// 메세지 보내기
if (sendMessage.value.isNotEmpty()) {
Log.d("새로운 메세지", sendMessage.value)
timestamp.value = System.currentTimeMillis()
//
val message = RdbMessageData(sendMessage.value, false, timestamp.value, userId)
viewModel.newMessage(chatId, message)
sendMessage.value = ""
}
}
){
Icon(imageVector = Icons.Filled.Send,
contentDescription = "보내기", tint = colorResource(id = R.color.green))
}
},
modifier = Modifier
.fillMaxWidth()
.padding(10.dp),
colors = TextFieldDefaults.outlinedTextFieldColors(focusedBorderColor = colorResource(id = R.color.green))
)
}