Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
59 commits
Select commit Hold shift + click to select a range
28a9eee
[PC-1300] MatchingWaiting 화면을 하나의 카드가 아닌 카드 리스트 뷰로 수정중
kkh725 Oct 20, 2025
3979980
[PC-1300] MatchingWaitingScreen 에 NewMatchingCard ui 추가
kkh725 Oct 20, 2025
ac6fa07
[PC-1300] MatchingUserScreen 리스트로 state 변환 작업 진행중
kkh725 Oct 20, 2025
189238d
[PC-1300] MatchingCard 상태 별 카드 UI, expanded, collapsed 분리 개발완료
kkh725 Oct 23, 2025
11ef29d
[PC-1300] 무료 즉시 매칭 가능 여부 api 추가
kkh725 Oct 28, 2025
99ec0c4
[PC-1300] 다중매칭용 타이머, 무료 맛보기 매칭 상태 추가
kkh725 Oct 29, 2025
1e9d769
Merge branch 'develop' of https://github.com/YAPP-Github/Piece-Androi…
kkh725 Nov 3, 2025
9341533
[PC-1300] 즉시매칭 toMe, fromMe api 연결
kkh725 Nov 3, 2025
9e58eee
[PC-1300] matchType(basic, from-me, to-me) 상태 추가,
kkh725 Nov 4, 2025
a38a337
[PC-1300] 불 필요한 getOpponentProfile 함수 제거
kkh725 Nov 4, 2025
fdb2051
[PC-1300] MatchCardOpen, 맛보기 매칭카드 에 대한 상태처리 추가
kkh725 Nov 7, 2025
fe05ba1
[PC-1300] 맛보기 매칭, 3종류 별 매칭 리스트에 대한 상태 정리
kkh725 Nov 7, 2025
82b26dc
[PC-1300] 다중 TimerJob 로직 구현, Expanded된 카드 Open 로직 구현중
kkh725 Nov 7, 2025
e4b6584
[PC-1300] 맛보기 매칭 관련 로직 실행 시점 변경
kkh725 Nov 7, 2025
c67367b
[PC-1300] 즉시 매칭 관련 로직 추가
kkh725 Nov 7, 2025
8cdb7f2
[PC-1300] 다중 타이머 로직 구현 완료
kkh725 Nov 9, 2025
82c1771
[PC-1300] 매칭 유료, 및 즉시매칭 추가 시 나오는 dialog UI 구현
kkh725 Nov 10, 2025
b0f49d3
[PC-1300] package 수정 및 함수명 변경
kkh725 Nov 11, 2025
d22e943
[PC-1300] MatchCard Expand, Collasped 애니메이션 추가
kkh725 Nov 11, 2025
c7b92b7
[PC-1300] MatchRepository 에서 3개의 매치조회를 하던 로직 -> ViewModel로 교체
kkh725 Nov 11, 2025
8a7b4c2
[PC-1300] MatchInfo 중 열린 카드의 id를 뷰모델에 저장하는 로직 추가.
kkh725 Nov 12, 2025
9204d36
[PC-1300] 스켈레톤 화면 재구성
kkh725 Nov 12, 2025
1e47444
[PC-1300] matchDetail 페이지 타이머 구현
kkh725 Nov 12, 2025
9876a2e
[PC-1300] isGreenLightTome 관련 카드 배경색 변경
kkh725 Nov 15, 2025
76ba9fc
[PC-1300] MatchInfoList 조회하는 로직 viewModel -> repoImpl
kkh725 Nov 15, 2025
ec512f5
[PC-1300] MatchDetailScreen TopBar 밸류픽, 밸류톡 텍스트 나오지 않던 이슈 대응
kkh725 Nov 15, 2025
23f083b
[PC-1300] MatchInfo 데이터클래스의
kkh725 Nov 15, 2025
b22629b
merge-develop
kkh725 Nov 15, 2025
b30ac63
[PC-1300] lazyColumn items에 key값 추가
kkh725 Nov 18, 2025
625c263
[PC-1300] 카드 expand, collasped 애니메이션 구현
kkh725 Nov 18, 2025
e3c8233
[PC-1300] DialogType 재정의
kkh725 Nov 18, 2025
8b64632
[PC-1300] 사용하지 않는 코드 제거
kkh725 Nov 19, 2025
cc43e52
[PC-1300] 하드코딩 문자열 R String 으로 변경
kkh725 Nov 19, 2025
f551d00
[PC-1300] 하드코딩 문자열 R String 으로 변경
kkh725 Nov 19, 2025
7334d68
[PC-1300] R String 수정
kkh725 Nov 19, 2025
1d87f50
[PC-1300] formatIsoToCustomFormat 함수 catch를 하지않고 그냥 에러 throw
kkh725 Nov 23, 2025
3f58bbe
[PC-1300] getRemainingTimeUntil24Hours 함수 catch를 하지않고 그냥 에러 throw
kkh725 Nov 23, 2025
1230891
[PC-1300] PieceTimerWithCloseTopBar 의 modifier 최 하단으로 위치변경
kkh725 Nov 23, 2025
875932d
[PC-1300] Domain의 MatchInfo 데이터클래스 Immutable 어노테이션 제거
kkh725 Nov 23, 2025
dead7b0
[PC-1300] /** 다중매칭 */ 주석제거
kkh725 Nov 23, 2025
5ddeb5d
[PC-1300] timerJobMap ConcurrentHashMap으로 타입 변경
kkh725 Nov 23, 2025
7a7e750
[PC-1300] sampleMatchInfo 에 대한 가시성 처리 추가
kkh725 Nov 23, 2025
10b20de
[PC-1300] NewMatchingCardPreview 에 대한 가시성 처리 추가
kkh725 Nov 23, 2025
e4f69ac
[PC-1300] MatchingPreviewProvider 구현
kkh725 Nov 23, 2025
40cea61
[PC-1300] MatchingWaitingScreen 파라미터 순서 변경
kkh725 Nov 23, 2025
bd306bb
[PC-1300] 사용하지않는 주석 제거
kkh725 Nov 23, 2025
3a2ce13
[PC-1300] main/card -> component/card로 위치 변경
kkh725 Nov 25, 2025
1d54142
[PC-1300] PiecePurchaseDialogBottom 프리뷰 추가
kkh725 Nov 25, 2025
a0ecddf
[PC-1300] MatchingCard height가 고정이던 로직 수정
kkh725 Nov 25, 2025
38c3d07
[PC-1300] TimeUtil 테스트 코드 작성
kkh725 Nov 25, 2025
0fd6c5e
[PC-1300] FakeMatchingDataSource.kt 임시 구현
kkh725 Nov 25, 2025
5cb7e86
[PC-1300] blocked 제거 후 isBlocked로 통일
kkh725 Nov 25, 2025
b277348
[PC-1300] FakeMatchingDataSource blocked 제거 후 isBlocked로 통일
kkh725 Nov 25, 2025
108ef0e
[PC-1300] ImmutableMap -> PersistentMap으로 변환하여 불필요한 객체생성 과정 제거
kkh725 Nov 25, 2025
b68170e
[PC-1300] basicMatch도 다른 매치조회 api 들과 동시에 호출
kkh725 Nov 25, 2025
5c05c7f
[PC-1300] getMatchInfoList 함수를 job으로 처리해 동시호출 처리하는 방향으로 수정
kkh725 Nov 25, 2025
4a14e83
[PC-1300] Timer 종료 flag 0L -> TIMEOUT_FLAG 로 변경
kkh725 Nov 26, 2025
63d59ad
[PC-1300] MatchingScreenTest blocked -> isBlocked
kkh725 Nov 26, 2025
06ea723
[PC-1300] MockInfo, Spy에 Matchinfo의 바뀐 파라미터 적용
kkh725 Nov 26, 2025
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
Original file line number Diff line number Diff line change
Expand Up @@ -177,5 +177,4 @@ class BillingHelperImpl @Inject constructor(
Log.d("BillingClient", "BillingClient release called, but it wasn't ready")
}
}

}
18 changes: 18 additions & 0 deletions core/common-ui/src/main/java/com/puzzle/common/ui/Animation.kt
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,9 @@ import androidx.compose.animation.AnimatedContent
import androidx.compose.animation.AnimatedContentScope
import androidx.compose.animation.AnimatedVisibility
import androidx.compose.animation.AnimatedVisibilityScope
import androidx.compose.animation.EnterTransition
import androidx.compose.animation.ExitTransition
import androidx.compose.animation.SizeTransform
import androidx.compose.animation.core.LinearEasing
import androidx.compose.animation.core.tween
import androidx.compose.animation.expandVertically
Expand Down Expand Up @@ -61,6 +64,21 @@ fun <S> PiecePageTransitionAnimation(
modifier = modifier,
)

@Composable
fun <S> PieceCardTransitionAnimation(
targetState: S,
modifier: Modifier = Modifier,
content: @Composable AnimatedContentScope.(targetState: S) -> Unit
) = AnimatedContent(
targetState = targetState,
transitionSpec = {
(EnterTransition.None togetherWith ExitTransition.None)
.using(SizeTransform(clip = false))
},
content = content,
modifier = modifier,
)

Comment on lines +67 to +81
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

이건 어떤 애니메이션인가요 ?! 영상이 없어서 확인하기가 힘드네요 ㅠㅠㅠㅠ

@Composable
fun PieceVisibleAnimation(
visible: Boolean,
Expand Down
27 changes: 27 additions & 0 deletions core/common/src/main/java/com/puzzle/common/TimeUtil.kt
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

p1) 유틸 함수 추가시에 테스트 코드 꼭 추가해주세요...!

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

추가 완료했습니다. 38c3d07

Original file line number Diff line number Diff line change
Expand Up @@ -67,6 +67,33 @@ fun getRemainingTimeInSec(): Long {
return Duration.between(now, targetTime).seconds
}

/**
* 주어진 과거 시각("yyyy.MM.dd.HH.mm.ss")으로부터
* 24시간이 지날 때까지 남은 초를 계산합니다.
*/
fun getRemainingTimeUntil24Hours(pastDateTimeString: String): Long {
val zoneId = ZoneId.of(SEOUL_TIME_ZONE)
val formatter = DateTimeFormatter.ofPattern("yyyy.MM.dd.HH.mm.ss")

val pastDateTime = LocalDateTime.parse(pastDateTimeString, formatter)
val targetTime = pastDateTime.plusHours(24)
val now = LocalDateTime.now(zoneId)

val remainingSeconds = Duration.between(now, targetTime).seconds
return if (remainingSeconds > 0) remainingSeconds else 0L // 0 이하일 경우 0으로 처리
}

/**
* 입력 형식: 항상 "2025-11-09T13:50:53.633789"
* 출력 형식: "yyyy.MM.dd.HH.mm.ss" 로 변환
*/
fun formatIsoToDate(isoString: String): String {
val inputFormatter = DateTimeFormatter.ISO_LOCAL_DATE_TIME
val outputFormatter = DateTimeFormatter.ofPattern("yyyy.MM.dd.HH.mm.ss")
val dateTime = LocalDateTime.parse(isoString, inputFormatter)
return dateTime.format(outputFormatter)
}

fun formatTimeToHourMinuteSecond(totalSeconds: Long): String {
val hours = totalSeconds / HOUR_IN_SECOND
val minutes = (totalSeconds % HOUR_IN_SECOND) / MINUTE_IN_SECOND
Expand Down
68 changes: 68 additions & 0 deletions core/common/src/test/kotlin/com/puzzle/common/TimeUtilTest.kt
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

👍👍👍

Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,11 @@ import org.junit.jupiter.api.Test
import org.junit.jupiter.api.assertThrows
import org.junit.jupiter.params.ParameterizedTest
import org.junit.jupiter.params.provider.ValueSource
import java.time.Clock
import java.time.LocalDateTime
import java.time.ZoneId
import java.time.format.DateTimeFormatter
import java.time.format.DateTimeParseException

class TimeUtilTest {

Expand Down Expand Up @@ -89,4 +93,68 @@ class TimeUtilTest {
fun `잘못된 형식의 생일 날짜는 false를 반환한다`(input: String) {
assertFalse(input.isValidBirthDateFormat())
}

/**
* 입력 형식: "2025-11-09T13:50:53.633789"
* 출력 형식: "yyyy.MM.dd.HH.mm.ss" (ISO 형식)
*/
@Test
fun `올바른 ISO 형식을 변환할 수 있다`() {
// given
val input = "2025-11-09T13:50:53.633789"

// when
val result = formatIsoToDate(input)

// then
assertEquals("2025.11.09.13.50.53", result)
}

@ParameterizedTest
@ValueSource(
strings = [
"2025-11-09 13:50:53", // 공백
"2025/11/09T13:50:53", // 슬래시
"abcd", // 문자열
"2025-13-40T99:99:99", // 알맞지 않은 날짜
"", // 빈 문자열
" ", // 공백만
"-1999-13-40T99:99:99", // 음수
]
)
fun `잘못된 형식을 변환하려고 하면 DateTimeParseException이 발생한다`(input: String) {
assertThrows<DateTimeParseException> {
formatIsoToDate(input)
}
}

@Test
fun `이미 24시간이 지난 경우 0을 반환한다`() {
val fixedNow = LocalDateTime.of(2025, 1, 1, 12, 0, 0)

val past = fixedNow.minusHours(30) // 30시간 전
val pastString = past.format(DateTimeFormatter.ofPattern("yyyy.MM.dd.HH.mm.ss"))

val result = getRemainingTimeUntil24Hours(pastString)

assertEquals(0L, result)
}

@ParameterizedTest
@ValueSource(
strings = [
"2025-11-09 13:50:53", // 공백
"2025/11/09T13:50:53", // 슬래시
"abcd", // 문자열
"2025-13-40T99:99:99", // 알맞지 않은 날짜
"", // 빈 문자열
" ", // 공백만
"-1999-13-40T99:99:99", // 음수
]
)
fun `잘못된 형식을 넣으면 DateTimeParseException을 발생시킨다`(input: String) {
assertThrows<DateTimeParseException> {
getRemainingTimeUntil24Hours(input)
}
}
}
Original file line number Diff line number Diff line change
@@ -1,17 +1,19 @@
package com.puzzle.data.repository

import com.puzzle.common.suspendRunCatching
import com.puzzle.datastore.datasource.matching.LocalMatchingDataSource
import com.puzzle.domain.model.error.HttpResponseException
import com.puzzle.domain.model.error.HttpResponseStatus
import com.puzzle.domain.model.match.MatchInfo
import com.puzzle.domain.model.match.MatchType
import com.puzzle.domain.model.profile.Contact
import com.puzzle.domain.model.profile.OpponentProfile
import com.puzzle.domain.model.profile.OpponentProfileBasic
import com.puzzle.domain.model.profile.OpponentValuePick
import com.puzzle.domain.model.profile.OpponentValueTalk
import com.puzzle.domain.repository.MatchingRepository
import com.puzzle.network.model.profile.ContactResponse
import com.puzzle.network.source.matching.MatchingDataSource
import kotlinx.coroutines.async
import kotlinx.coroutines.coroutineScope
import kotlinx.coroutines.supervisorScope
import javax.inject.Inject

class MatchingRepositoryImpl @Inject constructor(
Expand All @@ -33,14 +35,37 @@ class MatchingRepositoryImpl @Inject constructor(
return response.contacts.map(ContactResponse::toDomain)
}

override suspend fun getMatchInfo(): MatchInfo = matchingDataSource.getMatchInfo()
.toDomain()
override suspend fun getCanFreeMatch(): Boolean =
matchingDataSource.getCanFreeMatch().canFreeMatch

override suspend fun getNewInstantMatch(): Unit =
matchingDataSource.getNewInstantMatch()

override suspend fun getBasicMatchInfo() = matchingDataSource.getMatchInfo()
.toDomain(matchType = MatchType.BASIC)

override suspend fun getMatchInfoList(): List<MatchInfo> = coroutineScope {
val basicMatchDeferred = async {
suspendRunCatching {
listOf(matchingDataSource.getMatchInfo().toDomain(MatchType.BASIC))
}.getOrElse { throwable ->
if (throwable is HttpResponseException && throwable.status == HttpResponseStatus.NotFound) {
emptyList()
} else throw throwable
}
}

val toMeMatchListDeferred = async { matchingDataSource.getToMeMatchInfoList().map { it.toDomain(MatchType.TO_ME) } }
val fromMeMatchListDeferred = async { matchingDataSource.getFromMeMatchInfoList().map { it.toDomain(MatchType.FROM_ME) } }

basicMatchDeferred.await() + toMeMatchListDeferred.await() + fromMeMatchListDeferred.await()
}

override suspend fun getOpponentProfile(): OpponentProfile = coroutineScope {
val valueTalksDeferred = async { getOpponentValueTalks() }
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

이거 제거하신 이유 궁금해요~~

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

        val valueTalksDeferred = async { matchingDataSource.getOpponentValueTalks().toDomain() }
        val valuePicksDeferred = async { matchingDataSource.getOpponentValuePicks().toDomain() }
        val profileBasicDeferred = async { matchingDataSource.getOpponentProfileBasic().toDomain() }
        val profileImageDeferred = async { matchingDataSource.getOpponentProfileImage().toDomain() }

위와 같이 toDomain 만 달려있는 함수를 굳이 따로 분리해서 만들어야하는가 에 대한 생각이 들어서 제거했습니다!

val valuePicksDeferred = async { getOpponentValuePicks() }
val profileBasicDeferred = async { getOpponentProfileBasic() }
val profileImageDeferred = async { getOpponentProfileImage() }
val valueTalksDeferred = async { matchingDataSource.getOpponentValueTalks().toDomain() }
val valuePicksDeferred = async { matchingDataSource.getOpponentValuePicks().toDomain() }
val profileBasicDeferred = async { matchingDataSource.getOpponentProfileBasic().toDomain() }
val profileImageDeferred = async { matchingDataSource.getOpponentProfileImage().toDomain() }

val valuePicks = valuePicksDeferred.await()
val valueTalks = valueTalksDeferred.await()
Expand All @@ -64,18 +89,6 @@ class MatchingRepositoryImpl @Inject constructor(
)
}

private suspend fun getOpponentValueTalks(): List<OpponentValueTalk> =
matchingDataSource.getOpponentValueTalks().toDomain()

private suspend fun getOpponentValuePicks(): List<OpponentValuePick> =
matchingDataSource.getOpponentValuePicks().toDomain()

private suspend fun getOpponentProfileBasic(): OpponentProfileBasic =
matchingDataSource.getOpponentProfileBasic().toDomain()

private suspend fun getOpponentProfileImage(): String =
matchingDataSource.getOpponentProfileImage().toDomain()

override suspend fun checkMatchingPiece() = matchingDataSource.checkMatchingPiece()

override suspend fun acceptMatching() = matchingDataSource.acceptMatching()
Expand Down
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
package com.puzzle.data.source.matching

import com.puzzle.network.model.matching.GetCanFreeMatchResponse
import com.puzzle.network.model.matching.GetContactsResponse
import com.puzzle.network.model.matching.GetMatchInfoResponse
import com.puzzle.network.model.matching.GetOpponentProfileBasicResponse
Expand All @@ -14,6 +15,43 @@ class FakeMatchingDataSource : MatchingDataSource {
private var opponentValueTalksData: GetOpponentValueTalksResponse? = null
private var opponentProfileImageData: GetOpponentProfileImageResponse? = null

override suspend fun getCanFreeMatch(): GetCanFreeMatchResponse = GetCanFreeMatchResponse(false)
override suspend fun getToMeMatchInfoList(): List<GetMatchInfoResponse> =
listOf(
GetMatchInfoResponse(
matchId = 1234,
matchStatus = "WAITING",
description = "안녕하세요, 저는 음악과 여행을 좋아하는 사람입니다.",
nickname = "여행하는음악가",
birthYear = "1995",
location = "서울특별시",
job = "음악 프로듀서",
matchedUserId = 1,
isBlocked = false,
matchedValueCount = 3,
matchedValueList = listOf("음악", "여행", "독서")
)
)

override suspend fun getFromMeMatchInfoList(): List<GetMatchInfoResponse> =
listOf(
GetMatchInfoResponse(
matchId = 1234,
matchStatus = "WAITING",
description = "안녕하세요, 저는 음악과 여행을 좋아하는 사람입니다.",
nickname = "여행하는음악가",
birthYear = "1995",
location = "서울특별시",
job = "음악 프로듀서",
matchedUserId = 1,
isBlocked = false,
matchedValueCount = 3,
matchedValueList = listOf("음악", "여행", "독서")
)
)

override suspend fun getNewInstantMatch() {}

fun setOpponentProfileData(
basicData: GetOpponentProfileBasicResponse,
valuePicksData: GetOpponentValuePicksResponse,
Expand Down Expand Up @@ -68,7 +106,7 @@ class FakeMatchingDataSource : MatchingDataSource {
location = "서울특별시",
job = "음악 프로듀서",
matchedUserId = 1,
blocked = false,
isBlocked = false,
matchedValueCount = 3,
matchedValueList = listOf("음악", "여행", "독서")
)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,7 @@ import androidx.compose.ui.graphics.Brush
import androidx.compose.ui.graphics.Color
import androidx.compose.ui.graphics.Shape
import androidx.compose.ui.res.painterResource
import androidx.compose.ui.text.style.TextAlign
import androidx.compose.ui.tooling.preview.Preview
import androidx.compose.ui.unit.dp
import com.puzzle.designsystem.R
Expand Down Expand Up @@ -123,6 +124,7 @@ fun PieceSubButton(
) {
Text(
text = label,
textAlign = TextAlign.Center,
style = PieceTheme.typography.bodyMSB,
)
}
Expand Down
Loading
Loading