-
Notifications
You must be signed in to change notification settings - Fork 0
feat: 홈 화면 UI 구현 #23
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
feat: 홈 화면 UI 구현 #23
Changes from all commits
22dfd7b
ca61e20
34b6413
4224e4a
1ba4013
5ce2bb1
6aba72d
22aa4d0
fde5d7e
82794d2
6b864ea
f43fdef
7969c95
6ddeb25
b27d72a
28487cd
9f54b47
d933d0a
f0e0429
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.
Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,87 @@ | ||
| package kr.co.call.core.common.util | ||
|
|
||
| import java.time.Duration | ||
| import java.time.LocalDate | ||
| import java.time.LocalDateTime | ||
| import java.time.format.DateTimeFormatter | ||
| import java.util.Locale | ||
|
|
||
| /** | ||
| * 날짜 및 시간과 관련된 변환 및 포맷팅 처리를 담당하는 유틸리티 객체입니다. | ||
| * | ||
| * 주로 한국어 로케일을 기반으로 통화 내역, 홈 화면, 예약 시간 등에 사용되는 | ||
| * 다양한 형식의 문자열 변환 기능을 제공합니다. | ||
| */ | ||
| object TimeUtil { | ||
|
|
||
| // LocalDateTime을 String으로 변환 | ||
| fun parseLocalDateTime(value: String): LocalDateTime = | ||
| LocalDateTime.parse(value) | ||
|
|
||
| // LocalDate를 String으로 변환 | ||
| fun toCallHistoryDateText(dateTime: LocalDateTime): String = | ||
| dateTime.format( | ||
| DateTimeFormatter.ofPattern( | ||
| "M월 d일 a h시 m분", | ||
| Locale.KOREAN, | ||
| ), | ||
| ) | ||
|
|
||
| // LocalDateTime을 "n분 전" 형식의 문자열로 변환 | ||
| fun toTimeAgoText( | ||
| dateTime: LocalDateTime, | ||
| now: LocalDateTime = LocalDateTime.now(), | ||
| ): String { | ||
| val elapsedMinutes = Duration.between(dateTime, now) | ||
| .toMinutes() | ||
| .coerceAtLeast(0) | ||
|
|
||
| return when { | ||
| elapsedMinutes < 60 -> "${elapsedMinutes}분 전" | ||
| elapsedMinutes < 60 * 24 -> "${elapsedMinutes / 60}시간 전" | ||
| else -> "${elapsedMinutes / (60 * 24)}일 전" | ||
| } | ||
| } | ||
|
|
||
| // LocalDate를 "M월 d일 EEEE" 형식의 문자열로 변환 | ||
| fun toHomeDateText(date: LocalDate): String = | ||
| date.format( | ||
| DateTimeFormatter.ofPattern( | ||
| "M월 d일 EEEE", | ||
| Locale.KOREAN, | ||
| ), | ||
| ) | ||
|
|
||
| // LocalDateTime을 "오늘/내일 + 시간" 형식의 문자열로 변환 | ||
| fun toReservationTimeText( | ||
| dateTime: LocalDateTime, | ||
| today: LocalDate = LocalDate.now(), | ||
| ): String { | ||
| val dateText = when (dateTime.toLocalDate()) { | ||
| today -> "오늘" | ||
| today.plusDays(1) -> "내일" | ||
| else -> dateTime.format(DateTimeFormatter.ofPattern("M월 d일")) | ||
| } | ||
| val periodText = when (dateTime.hour) { | ||
| in 0..5 -> "새벽" | ||
| in 6..11 -> "아침" | ||
| in 12..17 -> "낮" | ||
| in 18..20 -> "저녁" | ||
| else -> "밤" | ||
| } | ||
| val hourText = when (val hourOf12 = dateTime.hour % 12) { | ||
| 0 -> 12 | ||
| else -> hourOf12 | ||
| } | ||
| val minuteText = if (dateTime.minute == 0) { | ||
| "" | ||
| } else { | ||
| " ${dateTime.minute}분" | ||
| } | ||
|
|
||
| return "$dateText $periodText ${hourText}시$minuteText" | ||
| } | ||
|
|
||
| fun toHourMinuteText(dateTime: LocalDateTime): String = | ||
| dateTime.format(DateTimeFormatter.ofPattern("HH:mm")) | ||
| } | ||
| Original file line number | Diff line number | Diff line change | ||||||||||||||||||||||||||||||||||||
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
| @@ -0,0 +1,62 @@ | ||||||||||||||||||||||||||||||||||||||
| package kr.co.call.data.mapper | ||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||
| import java.time.LocalDateTime | ||||||||||||||||||||||||||||||||||||||
| import kr.co.call.core.common.util.TimeUtil | ||||||||||||||||||||||||||||||||||||||
| import kr.co.call.domain.model.home.CallHistory | ||||||||||||||||||||||||||||||||||||||
| import kr.co.call.domain.model.home.CallReservation | ||||||||||||||||||||||||||||||||||||||
| import kr.co.call.domain.model.home.CallReservations | ||||||||||||||||||||||||||||||||||||||
| import kr.co.call.domain.model.home.HomeSummary | ||||||||||||||||||||||||||||||||||||||
| import kr.co.call.network.dto.CallHistoryDto | ||||||||||||||||||||||||||||||||||||||
| import kr.co.call.network.dto.HomeSummaryDto | ||||||||||||||||||||||||||||||||||||||
| import kr.co.call.network.dto.ReservationListDto | ||||||||||||||||||||||||||||||||||||||
| import kr.co.call.network.dto.ReservationDto | ||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||
| internal fun CallHistoryDto.toDomain(): CallHistory { | ||||||||||||||||||||||||||||||||||||||
| val parsedStartedAt = TimeUtil.parseLocalDateTime(startedAt) | ||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||
| return CallHistory( | ||||||||||||||||||||||||||||||||||||||
| callId = callId, | ||||||||||||||||||||||||||||||||||||||
| characterName = characterName, | ||||||||||||||||||||||||||||||||||||||
| aiSummary = aiSummary, | ||||||||||||||||||||||||||||||||||||||
| startedAt = parsedStartedAt, | ||||||||||||||||||||||||||||||||||||||
| isOutgoing = sender.toIsOutgoing(), | ||||||||||||||||||||||||||||||||||||||
| isMissed = status.toIsMissed(), | ||||||||||||||||||||||||||||||||||||||
| ) | ||||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||
| private fun String.toIsOutgoing(): Boolean = | ||||||||||||||||||||||||||||||||||||||
| when (this) { | ||||||||||||||||||||||||||||||||||||||
| "USER" -> true | ||||||||||||||||||||||||||||||||||||||
| "AI" -> false | ||||||||||||||||||||||||||||||||||||||
| else -> error("지원하지 않는 통화 발신자입니다: $this") | ||||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||
| private fun String.toIsMissed(): Boolean = | ||||||||||||||||||||||||||||||||||||||
| when (this) { | ||||||||||||||||||||||||||||||||||||||
| "COMPLETED" -> false | ||||||||||||||||||||||||||||||||||||||
| "MISSED" -> true | ||||||||||||||||||||||||||||||||||||||
| else -> error("지원하지 않는 통화 상태입니다: $this") | ||||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||
| internal fun ReservationListDto.toDomain(): CallReservations = | ||||||||||||||||||||||||||||||||||||||
| CallReservations( | ||||||||||||||||||||||||||||||||||||||
| totalCount = count, | ||||||||||||||||||||||||||||||||||||||
| items = content.map { it.toDomain() }, | ||||||||||||||||||||||||||||||||||||||
| ) | ||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||
| private fun ReservationDto.toDomain(): CallReservation = | ||||||||||||||||||||||||||||||||||||||
| CallReservation( | ||||||||||||||||||||||||||||||||||||||
| id = callReservationId, | ||||||||||||||||||||||||||||||||||||||
| characterId = characterId, | ||||||||||||||||||||||||||||||||||||||
| firstName = firstName, | ||||||||||||||||||||||||||||||||||||||
| imageUrl = imageUrl, | ||||||||||||||||||||||||||||||||||||||
| scheduledAt = LocalDateTime.parse(scheduledAt), | ||||||||||||||||||||||||||||||||||||||
| ) | ||||||||||||||||||||||||||||||||||||||
|
Comment on lines
+47
to
+54
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 🎯 Functional Correctness | 🟠 Major | ⚡ Quick win 날짜 파싱 방식이
🔧 제안 수정- scheduledAt = LocalDateTime.parse(scheduledAt),
+ scheduledAt = TimeUtil.parseLocalDateTime(scheduledAt),📝 Committable suggestion
Suggested change
🤖 Prompt for AI Agents |
||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||
| internal fun HomeSummaryDto.toDomain(): HomeSummary = | ||||||||||||||||||||||||||||||||||||||
| HomeSummary( | ||||||||||||||||||||||||||||||||||||||
| firstName = firstName, | ||||||||||||||||||||||||||||||||||||||
| relationshipDays = relationshipDays, | ||||||||||||||||||||||||||||||||||||||
| totalCallCount = totalCallCount, | ||||||||||||||||||||||||||||||||||||||
| callStreakDays = callStreakDays, | ||||||||||||||||||||||||||||||||||||||
| ) | ||||||||||||||||||||||||||||||||||||||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,85 @@ | ||
| package kr.co.call.data.repositoryImpl | ||
|
|
||
| import java.time.LocalDateTime | ||
| import javax.inject.Inject | ||
| import kr.co.call.domain.model.home.CallHistory | ||
| import kr.co.call.domain.model.home.CallReservation | ||
| import kr.co.call.domain.model.home.CallReservations | ||
| import kr.co.call.domain.model.home.HomeSummary | ||
| import kr.co.call.domain.repository.HomeRepository | ||
|
|
||
| class HomeRepositoryImpl @Inject constructor() : HomeRepository { | ||
|
|
||
| override suspend fun getReservations(): Result<CallReservations> = | ||
| Result.success( | ||
| CallReservations( | ||
| totalCount = 1, | ||
| items = listOf( | ||
| CallReservation( | ||
| id = 3L, | ||
| characterId = 2L, | ||
| firstName = "민준", | ||
| imageUrl = null, | ||
| scheduledAt = LocalDateTime.of(2026, 6, 30, 21, 0), | ||
| ), | ||
| ), | ||
| ), | ||
| ) | ||
|
|
||
| override suspend fun getCallHistories(): Result<List<CallHistory>> = | ||
| Result.success( | ||
| List(CALL_HISTORY_SIZE) { index -> | ||
| CallHistory( | ||
| callId = (index + 1).toLong(), | ||
| characterName = if (index % 2 == 0) "민준" else "동휘", | ||
| aiSummary = if (index % 2 == 0) { | ||
| "오늘 하루와 퇴근 후 일상 이야기" | ||
| } else { | ||
| "몸살 감기 기운과 걱정해주는 이야기" | ||
| }, | ||
| startedAt = LocalDateTime.of( | ||
| 2026, | ||
| 6, | ||
| (28 - index).coerceAtLeast(1), | ||
| 23, | ||
| 2, | ||
| ), | ||
| isOutgoing = index % 2 == 0, | ||
| isMissed = index % 4 == 3, | ||
| ) | ||
| }, | ||
| ) | ||
|
|
||
| override suspend fun getSummary(): Result<HomeSummary> = | ||
| Result.success( | ||
| HomeSummary( | ||
| firstName = "수현", | ||
| relationshipDays = 30, | ||
| totalCallCount = 24, | ||
| callStreakDays = 12, | ||
| ), | ||
| ) | ||
|
|
||
| override suspend fun changeReservationTime( | ||
| reservationId: Long, | ||
| scheduledAt: LocalDateTime, | ||
| ): Result<CallReservations> = | ||
| Result.success( | ||
| CallReservations( | ||
| totalCount = 1, | ||
| items = listOf( | ||
| CallReservation( | ||
| id = reservationId, | ||
| characterId = 2L, | ||
| firstName = "민준", | ||
| imageUrl = null, | ||
| scheduledAt = scheduledAt, | ||
| ), | ||
| ), | ||
| ), | ||
| ) | ||
|
|
||
| private companion object { | ||
| const val CALL_HISTORY_SIZE = 20 | ||
| } | ||
| } |
Uh oh!
There was an error while loading. Please reload this page.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
지나가는 안드 나그네 입니다...!
이건 core common에서 공통적으로 사용하는 유틸이기에 KDOC로 주석 달면 편하게 볼 수 있을 것 같아요:)
https://velog.io/@dudgns0507/Kotlin-KDoc%EC%9C%BC%EB%A1%9C-%EC%BD%94%ED%8B%80%EB%A6%B0-%EC%BD%94%EB%93%9C-%EB%AC%B8%EC%84%9C%ED%99%94%ED%95%98%EA%B8%B0-feat.-Dokka
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
@codebidoof 잠깐만 지나가겠습니다....
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
ㄱㅊ아영