Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
47 commits
Select commit Hold shift + click to select a range
8ed6ad1
feat: 대화방 입력창 컴포넌트 적용
seunghee17 Aug 15, 2026
8f24b91
feat: GamssInputBar preview 추가
seunghee17 Aug 15, 2026
29c24fd
feat: TopNavigation 오른쪽 다중 아이콘 추가에 따른 Ui 변경
seunghee17 Aug 15, 2026
d98a398
feat: GamssInputBar 메시지 답장 정보 추가에 따른 ui 변경
seunghee17 Aug 15, 2026
3f34241
feat: 위험신호 다이얼로그 ui 수정
seunghee17 Aug 15, 2026
f407ee8
feat: GamssButton 배경색상 변경
seunghee17 Aug 15, 2026
28356e9
feat: 대화방 상세 화면 UI 동작 연결
seunghee17 Aug 15, 2026
0525149
feat: 대화 상세 조회 API 연동 및 생성일 표시
seunghee17 Aug 15, 2026
22b9bb3
feat: 채팅 송수신 애니메이션 적용
seunghee17 Aug 15, 2026
cd92856
fix: 채팅 메시지 진입 애니메이션 자연스럽게 개선
seunghee17 Aug 16, 2026
5ac53f6
fix: 전송 중에도 입력창 포커스와 키보드 유지
seunghee17 Aug 16, 2026
664441c
feat: ChatRoomScreen에 사용되는 컴포넌트 파일 분리
seunghee17 Aug 16, 2026
2bb84a0
fix: 채팅 메시지 진입 애니메이션 자연스럽게 개선
seunghee17 Aug 16, 2026
08d4c54
feat: 메시지 전송 인터랙션 및 입력중, 스크롤 상태 추가
seunghee17 Aug 16, 2026
f7ea001
feat: 토큰 사용량 조회 toggle 생성
seunghee17 Aug 17, 2026
c8e4af6
chore: 채팅방 화면 detekt 포맷팅 이슈 정리
seunghee17 Aug 17, 2026
a431ba2
chore: detekt 포맷팅 이슈 정리 및 conflict 해결
seunghee17 Aug 17, 2026
85eedfa
chore: test 이슈 정리
seunghee17 Aug 17, 2026
030ab63
fix: GamssTopNavigation이 GamssTheme의 강제 라이트 설정을 따르지 않던 문제 수정
seunghee17 Aug 17, 2026
3ef872b
fix: 채팅 버블 vector drawable 과다 pathData로 인한 크래시 수정
seunghee17 Aug 17, 2026
2270e6c
feat: 감정별 채팅 프로필 이미지 추가
seunghee17 Aug 17, 2026
0a3ec04
fix: 카드 생성 아이콘 교체
seunghee17 Aug 17, 2026
305f33e
feat: 채팅 리스트 검색창 커서 색상 변경
seunghee17 Aug 17, 2026
8866d25
fix: 로컬 검증 오류에 따른 수정
seunghee17 Aug 17, 2026
638d378
fix: reveal 캐시 경로에서도 채팅방 title(생성 일시)을 즉시 채우기
seunghee17 Aug 17, 2026
1b2995e
fix: 카드 생성 중 로딩 인디케이터 복원
seunghee17 Aug 18, 2026
3334d41
fix: selection mode에서 검색 아이콘 숨김 복원
seunghee17 Aug 18, 2026
bccfe22
fix: ChatRoomViewModel.onSend()에서 메시지 reduce를 토큰 사용량 조회 완료까지 기다리지 않도록 분리
seunghee17 Aug 18, 2026
ad8fce0
fix: 전송 중 send 버튼만 비활성화 되도록 수정
seunghee17 Aug 18, 2026
ff9844e
fix: 채팅방 새 메시지 토스트가 스크롤해도 안 사라지던 문제 수정
seunghee17 Aug 18, 2026
6d4ccb7
Merge remote-tracking branch 'origin/develop' into feature/conversati…
seunghee17 Aug 18, 2026
7fc5fd3
fix: 누락 매개변수 추가
seunghee17 Aug 18, 2026
1af7b7b
Merge remote-tracking branch 'origin/develop' into feature/conversati…
seunghee17 Aug 18, 2026
3b34a1e
feat: 스크롤이 최하단일 때 상대 메시지가 오면 자동으로 최하단으로 스크롤 되도록 수정
seunghee17 Aug 18, 2026
ecd1fa9
feat: 다음 답변 pending되었을때 입력중 상태로 표현하기 위한 감정 이름, 프로필 표시 기능 구현
seunghee17 Aug 18, 2026
b6ce06c
fix: 사용자가 전송중일때는 캐릭터 입력중 상태 뜨지 않도록 수정
seunghee17 Aug 18, 2026
e62ca53
fix:메시지 전송 및 스크롤 애니메이션 성능 개선
seunghee17 Aug 18, 2026
a567b92
Merge remote-tracking branch 'origin/develop' into feature/conversati…
seunghee17 Aug 18, 2026
60a2dbc
fix: merge conflict 해결
seunghee17 Aug 18, 2026
a127cf0
fix: 네비게이션바 띄워서 채팅입력 표시되도록 수정
seunghee17 Aug 18, 2026
ca646b8
fix: 맨밑으로 스크롤시 별도의 애니메이션 없이 즉시 옮겨지도록 수정
seunghee17 Aug 18, 2026
f7f484f
fix: unused import 수정
seunghee17 Aug 18, 2026
005f90b
refactor: ChatScrollState 응집도 개선 및 스크롤 감지 비용 절감
seunghee17 Aug 18, 2026
a72fe08
Merge remote-tracking branch 'origin/develop' into feature/conversati…
seunghee17 Aug 18, 2026
372e6d1
fix: develop에서 누락된 KoreanTimeZone 정의 추가로 CI 빌드 실패 수정
seunghee17 Aug 18, 2026
0da0953
feat: 토큰 사용량 팝업에 조회 중 로딩 인디케이터 표시
seunghee17 Aug 19, 2026
c923e92
fix: 전송 실패 시 채팅 스크롤이 바닥으로 점프하지 않도록 수정
seunghee17 Aug 19, 2026
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions app/src/main/AndroidManifest.xml
Original file line number Diff line number Diff line change
Expand Up @@ -51,6 +51,7 @@
android:exported="true"
android:screenOrientation="portrait"
android:theme="@style/Theme.GAMSS"
android:windowSoftInputMode="adjustResize"
tools:ignore="DiscouragedApi,LockedOrientationActivity">
<intent-filter>
<action android:name="android.intent.action.MAIN" />
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -178,6 +178,7 @@ private fun mainEntryProvider(navigator: Navigator) = entryProvider {
ChatRoomScreen(
conversationId = key.conversationId,
onCardClose = navigator::goBack,
onBackClick = navigator::goBack,
)
}
}
Expand Down
Original file line number Diff line number Diff line change
@@ -1,34 +1,24 @@
package com.gamss.android.core.common.util

import java.time.Instant
import java.time.LocalDateTime
import java.time.ZoneId
import java.time.format.DateTimeFormatter
import java.time.format.DateTimeParseException
import java.util.Locale

/** 기기 시간대와 무관하게 한국 기준으로 "오늘"/"이번 달"을 고정해야 할 때 쓴다. */
val KoreanTimeZone: ZoneId = ZoneId.of("Asia/Seoul")

private const val KOREAN_TIME_PATTERN = "a h:mm"

val KoreanTimeZone: ZoneId = ZoneId.of("Asia/Seoul")
private val KoreanTimeFormatter: DateTimeFormatter = DateTimeFormatter
.ofPattern(KOREAN_TIME_PATTERN, Locale.KOREAN)
.withZone(KoreanTimeZone)
private const val CONVERSATION_DATE_PATTERN = "yy.MM.dd"

// 존을 덮지 않는다. 이미 벽시계로 변환된 값을 그대로 찍는 용도다.
private val KoreanWallClockFormatter: DateTimeFormatter =
DateTimeFormatter.ofPattern(KOREAN_TIME_PATTERN, Locale.KOREAN)

/**
* UTC ISO-8601 날짜 문자열을 한국 시간의 `오전/오후 h:mm` 형식으로 변환한다.
*
* [isoDateTime]이 ISO-8601 형식이 아니면 `null`을 반환한다.
*/
fun formatKoreanTime(isoDateTime: String): String? =
try {
KoreanTimeFormatter.format(Instant.parse(isoDateTime))
} catch (_: DateTimeParseException) {
null
}
// 불교력이나 일본력 로케일에서 연도가 밀리지 않게 ROOT 로 고정한다.
private val ConversationDateFormatter: DateTimeFormatter =
DateTimeFormatter.ofPattern(CONVERSATION_DATE_PATTERN, Locale.ROOT)

/**
* 이미 벽시계로 바뀐 시각을 `오전/오후 h:mm` 형식으로 변환한다.
Expand All @@ -37,3 +27,6 @@ fun formatKoreanTime(isoDateTime: String): String? =
* 기기 로케일이 영어여도 `AM/PM` 으로 바뀌지 않는다.
*/
fun formatKoreanTime(dateTime: LocalDateTime): String = KoreanWallClockFormatter.format(dateTime)

/** 대화방 생성 시각을 `yy.MM.dd` 형식의 날짜로 표시한다. */
fun formatConversationDate(dateTime: LocalDateTime): String = ConversationDateFormatter.format(dateTime)
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
package com.gamss.android.core.common.util

import kotlin.math.roundToInt

/**
* 오늘 사용한 토큰량을 오늘 하루 총 한도 대비 백분율로 환산한다.
*
* @param usedTokens 오늘 사용한 토큰 수.
* @param dailyLimit 오늘 하루 총 한도. 서버가 "한도 없음"을 null 로 내려줄 수 있어 nullable 이다.
* @return 0~100 사이로 clamp 된 정수 퍼센트. [dailyLimit] 이 null 이거나 0 이하라 나눌 수 없으면
* "0%"로 거짓 표시하지 않도록 null 을 반환한다 — 호출부가 무제한 상태를 구분해서 다뤄야 한다.
*/
fun calculateTokenUsagePercent(usedTokens: Long, dailyLimit: Long?): Int? {
if (dailyLimit == null || dailyLimit <= 0) return null

val percent = (usedTokens.toDouble() / dailyLimit.toDouble() * PERCENT_SCALE).roundToInt()
return percent.coerceIn(0, 100)
}

private const val PERCENT_SCALE = 100
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,9 @@ import androidx.compose.foundation.layout.fillMaxHeight
import androidx.compose.foundation.layout.fillMaxWidth
import androidx.compose.foundation.layout.height
import androidx.compose.foundation.layout.padding
import androidx.compose.foundation.layout.size
import androidx.compose.foundation.layout.width
import androidx.compose.material3.CircularProgressIndicator
import androidx.compose.material3.Text
import androidx.compose.runtime.Composable
import androidx.compose.ui.Alignment
Expand All @@ -28,12 +30,15 @@ private val TooltipContentGap = 6.dp
private val TooltipBorderWidth = 1.dp
private val ProgressBarHeight = 9.dp
private val TooltipShadowElevation = 40.dp
private val LoadingIndicatorSize = 24.dp
private val LoadingIndicatorStrokeWidth = 2.dp

/**
* 상단 내비게이션의 토큰 사용량 아이콘을 눌렀을 때 그 아래 뜨는 툴팁.
*
* [usagePercent]는 0~100 사이 값을 그대로 받는다 — 계산(도메인 값 유무 판단 등)은 호출부 책임이다.
* null이면 조회에 실패한 것으로 보고 [failMessage]와 재시도 액션([retryLabel]/[onRetryClick])을 대신 보여준다.
* [isLoading]이 true면 조회 중으로 보고 로딩 인디케이터를 대신 보여준다.
* 그 외에 null이면 조회에 실패한 것으로 보고 [failMessage]와 재시도 액션([retryLabel]/[onRetryClick])을 대신 보여준다.
*/
@Composable
fun GamssTokenUsageTooltip(
Expand All @@ -45,6 +50,7 @@ fun GamssTokenUsageTooltip(
retryLabel: String,
onRetryClick: () -> Unit,
modifier: Modifier = Modifier,
isLoading: Boolean = false,
) {
Column(
modifier = modifier
Expand All @@ -55,7 +61,23 @@ fun GamssTokenUsageTooltip(
.padding(TooltipPadding),
verticalArrangement = Arrangement.spacedBy(TooltipContentGap),
) {
if (usagePercent != null) {
if (isLoading) {
Text(
text = title,
style = GamssTheme.typography.subtitle4,
color = GamssTheme.colors.gray900,
)
Box(
modifier = Modifier.fillMaxWidth(),
contentAlignment = Alignment.Center,
) {
CircularProgressIndicator(
modifier = Modifier.size(LoadingIndicatorSize),
color = GamssTheme.colors.gray900,
strokeWidth = LoadingIndicatorStrokeWidth,
)
}
} else if (usagePercent != null) {
Row(
modifier = Modifier.fillMaxWidth(),
horizontalArrangement = Arrangement.SpaceBetween,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -165,10 +165,14 @@ fun GamssReceivedChatBubble(

/**
* 상대가 입력중일때 말풍선. 아바타 + 이름 + 말풍선이 왼쪽에 정렬된다.
*
* @param avatar 다음에 도착할 답장의 발신자를 이미 알 때(예: pendingComments) 그 캐릭터 아바타를
* 넘기면 빈 프로필 대신 표시한다.
*/
@Composable
fun GamssLoadingMessageBubble(
senderStatus: String
senderStatus: String,
avatar: (@Composable () -> Unit)? = null,
) {
Row(
horizontalArrangement = Arrangement.spacedBy(GamssTheme.spacing.spacing100),
Expand All @@ -178,7 +182,7 @@ fun GamssLoadingMessageBubble(
horizontalArrangement = Arrangement.spacedBy(GamssTheme.spacing.spacing100),
verticalAlignment = Alignment.Top,
) {
ChatAvatar(avatar = null)
ChatAvatar(avatar = avatar)
Column(verticalArrangement = Arrangement.spacedBy(GamssTheme.spacing.spacing075)) {
Text(
text = senderStatus,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@ package com.gamss.android.data.remote.conversation
import com.gamss.android.data.remote.conversation.model.request.SaveMessageRequest
import com.gamss.android.data.remote.conversation.model.request.UpdateConversationTitleRequest
import com.gamss.android.data.remote.conversation.model.response.ChattingRoomSearchResponse
import com.gamss.android.data.remote.conversation.model.response.ConversationMessage
import com.gamss.android.data.remote.conversation.model.response.ConversationDetailResponse
import com.gamss.android.data.remote.conversation.model.response.ConversationResponse
import com.gamss.android.data.remote.conversation.model.response.SaveMessageResponse
import com.gamss.android.data.remote.model.response.ApiResponse
Expand All @@ -23,8 +23,8 @@ internal interface ConversationService {
@POST("/api/conversations/messages")
suspend fun saveMessage(@Body request: SaveMessageRequest): ApiResponse<SaveMessageResponse>

@GET("/api/conversations/{conversationId}/messages")
suspend fun getMessages(@Path("conversationId") conversationId: Long): ApiResponse<List<ConversationMessage>>
@GET("/api/conversations/{conversationId}")
suspend fun getConversation(@Path("conversationId") conversationId: Long): ApiResponse<ConversationDetailResponse>

@PATCH("/api/conversations/{conversationId}/title")
suspend fun updateTitle(
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
package com.gamss.android.data.remote.conversation.model.response

import com.gamss.android.domain.conversation.ConversationDetail
import kotlinx.serialization.Serializable

@Serializable
internal data class ConversationDetailResponse(
val conversation: ConversationResponse,
val messages: List<ConversationMessage> = emptyList(),
)

internal fun ConversationDetailResponse.toDomain(): ConversationDetail? =
conversation.toDomain()?.let { conversation ->
ConversationDetail(
conversation = conversation,
messages = messages.map(ConversationMessage::toDomain),
)
}
Original file line number Diff line number Diff line change
Expand Up @@ -47,7 +47,7 @@ private fun ConversationMessage.toMessage(sender: MessageSender): Message = Mess
sender = sender,
content = content,
repliesToMessageId = repliesToMessageId,
createdTime = formatKoreanTime(createdAt)
createdTime = parseConversationCreatedAt(createdAt)?.let(::formatKoreanTime)
)

private fun ConversationMessage.resolveSender(): MessageSender = when (senderType) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -11,15 +11,14 @@ import com.gamss.android.data.di.ApplicationScope
import com.gamss.android.data.remote.conversation.ConversationService
import com.gamss.android.data.remote.conversation.model.request.SaveMessageRequest
import com.gamss.android.data.remote.conversation.model.request.UpdateConversationTitleRequest
import com.gamss.android.data.remote.conversation.model.response.ConversationMessage
import com.gamss.android.data.remote.conversation.model.response.ConversationResponse
import com.gamss.android.data.remote.conversation.model.response.toDomain
import com.gamss.android.data.remote.emotion.toServerEmotionType
import com.gamss.android.data.remote.runCatchingApiCall
import com.gamss.android.data.remote.throwIfFailed
import com.gamss.android.domain.conversation.Conversation
import com.gamss.android.domain.conversation.ConversationDetail
import com.gamss.android.domain.conversation.ConversationRepository
import com.gamss.android.domain.conversation.Message
import com.gamss.android.domain.conversation.SentMessage
import com.gamss.android.domain.conversation.chattingsearch.ChattingRoomSummary
import com.gamss.android.domain.emotion.EmotionCharacter
Expand Down Expand Up @@ -62,10 +61,9 @@ internal class ConversationRepositoryImpl @Inject constructor(
checkNotNull(response.data) { "No available saved message data" }.toDomain()
}

override suspend fun getMessages(conversationId: Long): AppResult<List<Message>> = runCatchingApiCall {
val response = conversationService.getMessages(conversationId)
checkNotNull(response.data) { "No available message data" }
.mapNotNull(ConversationMessage::toDomain)
override suspend fun getConversation(conversationId: Long): AppResult<ConversationDetail> = runCatchingApiCall {
val response = conversationService.getConversation(conversationId)
checkNotNull(response.data?.toDomain()) { "No available conversation detail data" }
}

override suspend fun updateTitle(conversationId: Long, title: String): AppResult<Unit> =
Expand Down
Original file line number Diff line number Diff line change
@@ -1,5 +1,7 @@
package com.gamss.android.data.remote.conversation

import com.gamss.android.data.remote.conversation.model.response.ConversationDetailResponse
import com.gamss.android.data.remote.conversation.model.response.ConversationMessage
import com.gamss.android.data.remote.conversation.model.response.ConversationResponse
import com.gamss.android.data.remote.conversation.model.response.parseConversationCreatedAt
import com.gamss.android.data.remote.conversation.model.response.toDomain
Expand Down Expand Up @@ -75,4 +77,27 @@ class ConversationResponseTest {
fun 빈_문자열_시각은_null_이다() {
assertNull(parseConversationCreatedAt(" "))
}

@Test
fun 대화_상세는_생성_시각과_메시지를_도메인으로_옮긴다() {
val detail = ConversationDetailResponse(
conversation = ConversationResponse(
id = 37,
title = "비 오는 날의 짜증",
createdAt = "2026-08-15T17:16:52.320",
),
messages = listOf(
ConversationMessage(
id = 1,
conversationId = 37,
senderType = ConversationMessage.SENDER_USER,
content = "오늘 억울한 일이 있었어",
createdAt = "2026-08-15T17:16:52.320",
),
),
).toDomain()

assertEquals(LocalDateTime.of(2026, 8, 15, 17, 16, 52, 320_000_000), detail?.conversation?.createdAt)
assertEquals(listOf(1L), detail?.messages?.map { it.id })
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
package com.gamss.android.domain.conversation

data class ConversationDetail(
val conversation: Conversation,
val messages: List<Message>,
)
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,7 @@ interface ConversationRepository {
excludeCharacters: Set<EmotionCharacter>,
): AppResult<SentMessage>

suspend fun getMessages(conversationId: Long): AppResult<List<Message>>
suspend fun getConversation(conversationId: Long): AppResult<ConversationDetail>

suspend fun updateTitle(conversationId: Long, title: String): AppResult<Unit>

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,7 @@ import javax.inject.Inject
@Suppress("LongParameterList")
class ConversationSession @Inject constructor(
private val sendMessage: SendMessageUseCase,
private val getMessages: GetMessagesUseCase,
private val getConversation: GetConversationUseCase,
private val updateConversationTitle: UpdateConversationTitleUseCase,
private val endConversation: EndConversationUseCase,
private val createConversationCard: CreateConversationCardUseCase,
Expand All @@ -30,12 +30,12 @@ class ConversationSession @Inject constructor(

private var pendingTitle: PendingTitle? = null

suspend fun restore(conversationId: Long): AppResult<List<Message>> {
suspend fun restore(conversationId: Long): AppResult<ConversationDetail> {
clearPendingTitle()
val result = getMessages(conversationId)
val result = getConversation(conversationId)
when (result) {
is AppResult.Success -> {
val utterances = result.data.userUtterances()
val utterances = result.data.messages.userUtterances()
summaryStore.restore(utterances)
emotionAccumulator.restore(utterances)
}
Expand Down Expand Up @@ -83,12 +83,12 @@ class ConversationSession @Inject constructor(
}

/** 홈 쪽 인스턴스에서만 채워진 요약·감정 상태를, [restore] 와 같은 방식으로 이 인스턴스에도 시드해 둔다. */
suspend fun consumePendingReveal(conversationId: Long): SentMessage? {
val sent = pendingReveal.consume(conversationId) ?: return null
val utterances = listOf(sent.message).userUtterances()
suspend fun consumePendingReveal(conversationId: Long): PendingReveal? {
val pending = pendingReveal.consume(conversationId) ?: return null
val utterances = listOf(pending.sent.message).userUtterances()
summaryStore.restore(utterances)
emotionAccumulator.restore(utterances)
return sent
return pending
}

suspend fun finishSend() = coroutineScope {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -4,10 +4,10 @@ import com.gamss.android.core.common.AppResult
import com.gamss.android.domain.usecase.UseCase
import javax.inject.Inject

class GetMessagesUseCase @Inject constructor(
class GetConversationUseCase @Inject constructor(
private val conversationRepository: ConversationRepository,
) : UseCase<Long, AppResult<List<Message>>> {
) : UseCase<Long, AppResult<ConversationDetail>> {

override suspend fun invoke(params: Long): AppResult<List<Message>> =
conversationRepository.getMessages(params)
override suspend fun invoke(params: Long): AppResult<ConversationDetail> =
conversationRepository.getConversation(params)
}
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,9 @@ package com.gamss.android.domain.conversation

import kotlinx.coroutines.sync.Mutex
import kotlinx.coroutines.sync.withLock
import java.time.Instant
import java.time.LocalDateTime
import java.time.ZoneId
import javax.inject.Inject
import javax.inject.Singleton

Expand All @@ -18,12 +21,26 @@ class PendingConversationReveal @Inject constructor() {
mutex.withLock { pending = Entry(sent, now) }
}

suspend fun consume(conversationId: Long, now: Long = System.currentTimeMillis()): SentMessage? =
suspend fun consume(conversationId: Long, now: Long = System.currentTimeMillis()): PendingReveal? =
mutex.withLock {
val current = pending?.takeIf { it.sent.message.conversationId == conversationId } ?: return@withLock null
pending = null
current.sent.takeIf { now - current.savedAtMillis <= PENDING_REVEAL_EXPIRY_MILLIS }
current.takeIf { now - it.savedAtMillis <= PENDING_REVEAL_EXPIRY_MILLIS }?.toPendingReveal()
}

private data class Entry(val sent: SentMessage, val savedAtMillis: Long)
private data class Entry(val sent: SentMessage, val savedAtMillis: Long) {
fun toPendingReveal() = PendingReveal(
sent = sent,
createdAt = Instant.ofEpochMilli(savedAtMillis).atZone(ZoneId.systemDefault()).toLocalDateTime(),
)
}
}

/**
* [PendingConversationReveal.consume] 결과. 서버 대화 상세를 다시 받아오기 전이라 [createdAt] 은
* 서버 값이 아니라 [PendingConversationReveal.save] 가 호출된(=대화가 막 생긴) 시각이다.
*/
data class PendingReveal(
val sent: SentMessage,
val createdAt: LocalDateTime,
)
Loading
Loading