Closed tgyuuAn closed 1 month ago
목표 -
문제 1 -
itemsIndexed(yearList) { index, year ->
Column(
modifier = Modifier.clickable { showSpinner = false },
) {
Text(
text = year,
style = BaekyoungTheme.typography.labelRegular.copy(fontSize = 13.sp),
color = BaekyoungTheme.colors.gray95,
textAlign = TextAlign.Center,
modifier = Modifier.padding(horizontal = 20.dp, vertical = 6.dp)
)
if (index < yearList.size - 1) {
HorizontalDivider(color = BaekyoungTheme.colors.grayAC)
}
}
}
}
문제 1의 원인 -
Horizontal Divider의 기본 Default Modifier가 fillMaxWidth()
였음.
문제 2 -
itemsIndexed(yearList) { index, year ->
Column(
modifier = Modifier
.width(IntrinsicSize.Min) // <-- 요 녀석 추가해서 Column 내부의 최소 width를 기준으로 그리도록 변경
.clickable { showSpinner = false },
) {
Text(
text = year,
style = BaekyoungTheme.typography.labelRegular.copy(fontSize = 13.sp),
color = BaekyoungTheme.colors.gray95,
textAlign = TextAlign.Center,
modifier = Modifier.padding(horizontal = 20.dp, vertical = 6.dp)
)
if (index < yearList.size - 1) {
HorizontalDivider(color = BaekyoungTheme.colors.grayAC)
}
}
}
근데 왜 Text에 개행이 될까요?
이건 아직도 잘 모르겠따.
여하튼 최종 -
itemsIndexed(yearList) { index, year ->
Column(
modifier = Modifier
.width(IntrinsicSize.Min)
.clickable { showSpinner = false },
) {
Text(
text = year,
style = BaekyoungTheme.typography.labelRegular.copy(fontSize = 13.sp),
color = BaekyoungTheme.colors.gray95,
textAlign = TextAlign.Center,
modifier = Modifier
.padding(horizontal = 20.dp, vertical = 6.dp)
.requiredWidth(IntrinsicSize.Max), // <-- 이 녀석을 추가해서 가능한 한 많이 차지하도록 변경
)
if (index < yearList.size - 1) {
HorizontalDivider(color = BaekyoungTheme.colors.grayAC)
}
}
}
네이쓰...!
근데 바로 유기
일단 아래와 같이 사용....! 레퍼런스
근데 지금은 이제 1:1 채팅방이라서 회원 정보는 없는.
@Entity(tableName = "chatting_room")
data class ChattingRoomEntity(
@PrimaryKey
val id: String,
@ColumnInfo(name = "last_chatting")
val lastChatting: String,
@ColumnInfo(name = "created_at")
val createdAt: String,
)
@Entity(tableName = "message")
data class MessageEntity(
@PrimaryKey
val id: String,
@ColumnInfo(name = "chatting_room_id")
val chattingRoomId: String,
@ColumnInfo(name = "message_from")
val messageFrom: String,
@ColumnInfo(name = "message_to")
val messageTo: String,
val content: String,
@ColumnInfo(name = "created_at")
val createdAt: String,
)
메세지에 from
, to
가 있는 이유는 AI 채팅과 유저 채팅의 구분 목적임.
추후에 멘토, 멘티 채팅을 고려하기도 했음.
지금 위에 Message
, ChattingRoomt
Entity가 총 2개가 있음.
근데 DAO를 설계할 때 어차피 둘 다 채팅 정보 하나의 목적을 가지고 있는데 ChattingDao
하나로 퉁칠지,
아니면 각 Entity 별로 DAO를 설계할 지 고민이 되었음.
근데 생각해보니까 뭐 엄청나게 거대한 규모도 아니고 1:1 채팅인데 그냥 하나의 DAO로 해결하면 좋을 것 같다고 판단.
무엇보다 귀찮으니까
@Dao
interface ChattingDao {
@Insert(onConflict = OnConflictStrategy.REPLACE)
suspend fun insertMessage(message: MessageEntity)
@Insert(onConflict = OnConflictStrategy.REPLACE)
suspend fun insertChattingRoom(chattingRoom: ChattingRoomEntity)
@Query("SELECT * FROM message WHERE chatting_room_id MATCH :roomId ORDER BY created_at DESC")
suspend fun getAllChattingRoomMessages(roomId: String): List<MessageEntity>
@Query("SELECT * FROM chatting_room ORDER BY created_at DESC")
suspend fun getAllChattingRoom(): List<ChattingRoomEntity>
}
Hoooooooooooolyyyyyyyyyyyyyyy 완전 맛있는 기능
https://firebase.google.com/docs/firestore/query-data/listen?hl=ko#view_changes_between_snapshots 에서 제공하는 addSnapshotListner
를 이용하면 Document 혹은 Collection의 변동 사항을 감지해서 리스너로 쏴준다.
즉, while
문 돌려서 DB를 일일이 확인할 필요가 없다는 마알쓰음.
override suspend fun getAllMessage(roomId: String): Flow<MentoringChatResponse> = callbackFlow {
val listenerRegistration = firebaseFirestore.collection(CHATTING_ROOM_COLLECTION)
.document(CHATTING_ROOM_COLLECTION)
.collection(roomId)
.addSnapshotListener { value, error ->
if (error != null){
Log.d("test", "listen:error", error)
return@addSnapshotListener
}
for (dc in value!!.documentChanges){
when(dc.type){
DocumentChange.Type.ADDED -> trySend(dc.document.toObject<MentoringChatResponse>())
DocumentChange.Type.MODIFIED -> Log.d("test", "Modified message: ${dc.document.data}")
DocumentChange.Type.REMOVED -> Log.d("test", "Removed message: ${dc.document.data}")
}
}
}
awaitClose { listenerRegistration.remove() }
}
이 때 응답은 callback으로 오기 때문에, callbackFlow를 이용해서 trySend()
살짝 쿵 먹여준다.
(이렇게 하는 이유는 addSnapshotListner 내부가 suspend 블럭이 아닌데, trySend()를 씀으로써 일반 함수 블럭 -> Flow 블럭으로 갖고 오게 해줌 크크)
awaitClose { listenerRegistration.remove() }
은 해당 코루틴이 cancel되거나 close 되었을 때 변화를 감지하는 listner를 닫는 역할!
작업 사항
채팅 로그 로컬에 저장!!!!!
Todo
기타사항