Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
44 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
990bef3
[PC-1259] onboarding에 lottie 의존성 추가
comst19 Nov 18, 2025
e9be2ee
[PC-1259] 리소스 제거 및 추가
comst19 Nov 18, 2025
7f32f9e
[PC-1259] onboarding 바뀐 ui 구현
comst19 Nov 18, 2025
1824371
[PC-1259] OnboardingData 패키지 이동
comst19 Nov 21, 2025
77ab60f
[PC-1259] onboardingPages, OnboardingPageProvider 패키지 이동
comst19 Nov 24, 2025
672e4dd
[PC-1259] SinglePlayLottie 컴포넌트 구현
comst19 Nov 24, 2025
80f32d7
[PC-1259] OnboardingPageContent -> PageContent
comst19 Nov 24, 2025
97fff39
[PC-1259] onboarding lottie 파일 추가
comst19 Nov 24, 2025
ad2d799
[PC-1259] onboardingPages 패키지 이동, 클래스명 변경 적용
comst19 Nov 24, 2025
5f2f99b
[PC-1259] lottie 파일 변경
comst19 Nov 25, 2025
cfc1d4d
[PC-1259] Lottie의 애니메이션이 99% 지점에서 멈추는 로직 추가
comst19 Nov 25, 2025
3346530
[PC-1259] PageContent 패키지 이동
comst19 Nov 25, 2025
98dbcab
[PC-1259] SinglePlayLottie -> StopAtProgressLottie
comst19 Nov 25, 2025
b26b89a
[PC-1259] 버튼으로만 다음 페이지로 이동할 수 있도록 변경
comst19 Nov 25, 2025
007de8c
[PC-1259] title fade in&out 추가
comst19 Nov 25, 2025
425570a
[PC-1259] 페이지 변경 시 애니메이션 제거
comst19 Nov 25, 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")
}
}

}
42 changes: 42 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,14 +4,18 @@ import androidx.compose.animation.AnimatedContent
import androidx.compose.animation.AnimatedContentScope
import androidx.compose.animation.AnimatedVisibility
import androidx.compose.animation.AnimatedVisibilityScope
import androidx.compose.animation.core.FastOutLinearInEasing
import androidx.compose.animation.core.LinearEasing
import androidx.compose.animation.core.LinearOutSlowInEasing
import androidx.compose.animation.core.tween
import androidx.compose.animation.expandVertically
import androidx.compose.animation.fadeIn
import androidx.compose.animation.fadeOut
import androidx.compose.animation.shrinkVertically
import androidx.compose.animation.togetherWith
import androidx.compose.foundation.layout.Box
import androidx.compose.runtime.Composable
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier

private const val BOTTOM_BAR_ANIMATION_DURATION = 700
Expand Down Expand Up @@ -74,6 +78,44 @@ fun PieceVisibleAnimation(
modifier = modifier,
)

@Composable
fun PieceExpandCollapseAnimation(
expanded: Boolean,
modifier: Modifier = Modifier,
contentWhenExpanded: @Composable () -> Unit,
contentWhenCollapsed: @Composable () -> Unit,
) {
Box(modifier = modifier) {
AnimatedVisibility(
visible = expanded,
enter = expandVertically(
expandFrom = Alignment.Top,
animationSpec = tween(durationMillis = AI_GUIDE_MESSAGE_ANIMATION_DURATION, easing = LinearOutSlowInEasing)
) + fadeIn(animationSpec = tween(AI_GUIDE_MESSAGE_ANIMATION_DURATION, easing = LinearOutSlowInEasing)),
exit = shrinkVertically(
shrinkTowards = Alignment.Top,
animationSpec = tween(durationMillis = AI_GUIDE_MESSAGE_ANIMATION_DURATION, easing = FastOutLinearInEasing)
) + fadeOut(animationSpec = tween(AI_GUIDE_MESSAGE_ANIMATION_DURATION, easing = FastOutLinearInEasing))
) {
contentWhenExpanded()
}

AnimatedVisibility(
visible = !expanded,
enter = expandVertically(
expandFrom = Alignment.Top,
animationSpec = tween(durationMillis = AI_GUIDE_MESSAGE_ANIMATION_DURATION, easing = LinearOutSlowInEasing)
) + fadeIn(animationSpec = tween(AI_GUIDE_MESSAGE_ANIMATION_DURATION, easing = LinearOutSlowInEasing)),
exit = shrinkVertically(
shrinkTowards = Alignment.Top,
animationSpec = tween(durationMillis = AI_GUIDE_MESSAGE_ANIMATION_DURATION, easing = FastOutLinearInEasing)
) + fadeOut(animationSpec = tween(AI_GUIDE_MESSAGE_ANIMATION_DURATION, easing = FastOutLinearInEasing))
) {
contentWhenCollapsed()
}
}
}

@Composable
fun PieceGuideMessageAnimation(
visible: Boolean,
Expand Down
35 changes: 35 additions & 0 deletions core/common/src/main/java/com/puzzle/common/TimeUtil.kt
Original file line number Diff line number Diff line change
Expand Up @@ -67,6 +67,41 @@ 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")

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

val remainingSeconds = Duration.between(now, targetTime).seconds
if (remainingSeconds > 0) remainingSeconds else 0L // 0 이하일 경우 0으로 처리
} catch (e: Exception) {
0L // 파싱 실패 시 0초 반환
}
}

/**
* 입력 형식: 항상 "2025-11-09T13:50:53.633789" (ISO_LOCAL_DATE_TIME 형태) GetMatchInfoResponse 응답
* 출력 형식: "yyyy.MM.dd.HH.mm.ss" 로 변환
*/
fun formatIsoToCustomFormat(isoString: String): String {
return try {
val inputFormatter = DateTimeFormatter.ISO_LOCAL_DATE_TIME
val outputFormatter = DateTimeFormatter.ofPattern("yyyy.MM.dd.HH.mm.ss")
val dateTime = LocalDateTime.parse(isoString, inputFormatter)
dateTime.format(outputFormatter)
} catch (e: Exception) {
"" // 파싱 실패 시 빈 문자열 반환
}
}

fun formatTimeToHourMinuteSecond(totalSeconds: Long): String {
val hours = totalSeconds / HOUR_IN_SECOND
val minutes = (totalSeconds % HOUR_IN_SECOND) / MINUTE_IN_SECOND
Expand Down
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,35 @@ 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 basicMatch = suspendRunCatching {
listOf(matchingDataSource.getMatchInfo().toDomain(MatchType.BASIC))
}.getOrElse { throwable ->
if (throwable is HttpResponseException && throwable.status == HttpResponseStatus.NotFound) {
emptyList()
} else throw throwable
}

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

basicMatch + toMeMatchList.await() + fromMeMatchList.await()
}

override suspend fun getOpponentProfile(): OpponentProfile = coroutineScope {
val valueTalksDeferred = async { getOpponentValueTalks() }
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 +87,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
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
Original file line number Diff line number Diff line change
Expand Up @@ -83,6 +83,32 @@ fun PieceDialogDefaultTop(
}
}

@Composable
fun PieceDialogDefaultTop(
title: AnnotatedString,
subText: AnnotatedString,
) {
Column(
verticalArrangement = Arrangement.spacedBy(8.dp),
horizontalAlignment = Alignment.CenterHorizontally,
modifier = Modifier.padding(top = 40.dp, bottom = 12.dp),
) {
Text(
text = title,
color = PieceTheme.colors.black,
textAlign = TextAlign.Center,
style = PieceTheme.typography.headingMSB,
)

Text(
text = subText,
color = PieceTheme.colors.dark2,
textAlign = TextAlign.Center,
style = PieceTheme.typography.bodySM,
)
}
}

@Composable
fun PieceDialogDefaultTop(
title: String,
Expand Down Expand Up @@ -188,6 +214,35 @@ fun PieceDialogBottom(
}
}

@Composable
fun PiecePurchaseDialogBottom(
imageId: Int,
leftButtonText: String,
rightButtonText: String,
onLeftButtonClick: () -> Unit,
onRightButtonClick: () -> Unit,
) {
Row(
horizontalArrangement = Arrangement.spacedBy(8.dp),
modifier = Modifier
.fillMaxWidth()
.padding(top = 12.dp, bottom = 20.dp),
) {
PieceOutlinedButton(
label = leftButtonText,
onClick = onLeftButtonClick,
modifier = Modifier.weight(1f),
)

PieceIconButton(
label = rightButtonText,
imageId = imageId,
onClick = onRightButtonClick,
modifier = Modifier.weight(1f),
)
}
}

@Composable
fun PieceImageDialog(
imageUri: Any?,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -185,6 +185,59 @@ fun PiecePuzzleTopBar(
}
}

@Composable
fun PieceTimerWithCloseTopBar(
modifier: Modifier = Modifier,
title: String,
remainTime: String,
contentColor: Color,
closeButtonEnabled: Boolean,
onCloseClick: () -> Unit,
) {
Box(
modifier = modifier
.fillMaxWidth()
.height(60.dp),
) {
if (!remainTime.isNotEmpty()) {
Row(
verticalAlignment = Alignment.CenterVertically,
modifier = Modifier.align(Alignment.CenterStart)
) {
Image(
painter = painterResource(R.drawable.ic_clock),
contentDescription = null,
colorFilter = ColorFilter.tint(PieceTheme.colors.error)
)
Text(
text = remainTime,
style = PieceTheme.typography.bodySM,
color = PieceTheme.colors.error,
)
}
}

Text(
text = title,
style = PieceTheme.typography.headingSSB,
color = contentColor,
modifier = Modifier.align(Alignment.Center),
)

if (closeButtonEnabled) {
Image(
painter = painterResource(R.drawable.ic_close),
contentDescription = "닫기 버튼",
colorFilter = ColorFilter.tint(contentColor),
modifier = Modifier
.size(32.dp)
.clickable { onCloseClick() }
.align(Alignment.CenterEnd),
)
}
}
}

@Preview
@Composable
fun PreviewPieceMainTopBar() {
Expand Down Expand Up @@ -226,6 +279,23 @@ fun PreviewPieceMainTopBarWithRightComponent() {
}
}

@Preview
@Composable
fun PreviewPieceMainTopBarWithRightComponent2() {
PieceTheme {
PieceTimerWithCloseTopBar(
remainTime = "01:01",
title = "title",
onCloseClick = {},
contentColor = PieceTheme.colors.black,
closeButtonEnabled = true,
modifier = Modifier
.padding(vertical = 20.dp)
.background(PieceTheme.colors.white)
)
}
}

@Preview
@Composable
fun PreviewPieceSubBackTopBar() {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@ private val Light1 = Color(0xFFCBD1D9)
private val Light2 = Color(0xFFE8EBF0)
private val Light3 = Color(0xFFF4F6FA)
private val White = Color(0xFFFFFFFF)
private val White60 = Color(0x99FFFFFF)

private val Error = Color(0xFFFF3059)

Expand All @@ -38,5 +39,6 @@ data class PieceColors(
val light2: Color = Light2,
val light3: Color = Light3,
val white: Color = White,
val white60: Color = White60,
val error: Color = Error,
)
12 changes: 12 additions & 0 deletions core/designsystem/src/main/res/drawable/badge_free.xml
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:width="50dp"
android:height="29dp"
android:viewportWidth="50"
android:viewportHeight="29">
<path
android:pathData="M14.641,0.899L35.641,0.899A14,14 0,0 1,49.641 14.899L49.641,14.899A14,14 0,0 1,35.641 28.899L14.641,28.899A14,14 0,0 1,0.641 14.899L0.641,14.899A14,14 0,0 1,14.641 0.899z"
android:fillColor="#FFE3E9"/>
<path
android:pathData="M13.508,10.415H18.77V11.528H14.797V14.094H18.395V15.196H14.797V18.899H13.508V10.415ZM20.023,12.536H21.23V13.543H21.301C21.418,13.204 21.623,12.936 21.916,12.741C22.209,12.542 22.543,12.442 22.918,12.442C23.098,12.442 23.273,12.45 23.445,12.465V13.661C23.375,13.645 23.275,13.629 23.146,13.614C23.021,13.598 22.902,13.59 22.789,13.59C22.504,13.59 22.246,13.653 22.016,13.778C21.785,13.899 21.604,14.069 21.471,14.288C21.342,14.502 21.277,14.747 21.277,15.02V18.899H20.023V12.536ZM26.914,19.028C26.297,19.028 25.76,18.893 25.303,18.624C24.85,18.354 24.5,17.973 24.254,17.481C24.012,16.989 23.891,16.415 23.891,15.758C23.891,15.11 24.012,14.536 24.254,14.036C24.5,13.536 24.846,13.147 25.291,12.87C25.736,12.592 26.25,12.454 26.832,12.454C27.344,12.454 27.813,12.565 28.238,12.788C28.664,13.01 29.006,13.362 29.264,13.842C29.525,14.319 29.656,14.922 29.656,15.653V16.098H25.133C25.145,16.493 25.225,16.834 25.373,17.124C25.525,17.409 25.734,17.625 26,17.774C26.266,17.922 26.574,17.997 26.926,17.997C27.266,17.997 27.553,17.934 27.787,17.809C28.021,17.68 28.195,17.512 28.309,17.305H29.574C29.48,17.649 29.313,17.952 29.07,18.213C28.828,18.471 28.521,18.672 28.15,18.817C27.783,18.958 27.371,19.028 26.914,19.028ZM28.426,15.161C28.426,14.836 28.359,14.547 28.227,14.293C28.098,14.04 27.914,13.842 27.676,13.702C27.438,13.557 27.16,13.485 26.844,13.485C26.52,13.485 26.23,13.561 25.977,13.713C25.727,13.862 25.529,14.065 25.385,14.323C25.24,14.577 25.158,14.856 25.139,15.161H28.426ZM33.641,19.028C33.023,19.028 32.486,18.893 32.029,18.624C31.576,18.354 31.227,17.973 30.98,17.481C30.738,16.989 30.617,16.415 30.617,15.758C30.617,15.11 30.738,14.536 30.98,14.036C31.227,13.536 31.572,13.147 32.018,12.87C32.463,12.592 32.977,12.454 33.559,12.454C34.07,12.454 34.539,12.565 34.965,12.788C35.391,13.01 35.732,13.362 35.99,13.842C36.252,14.319 36.383,14.922 36.383,15.653V16.098H31.859C31.871,16.493 31.951,16.834 32.1,17.124C32.252,17.409 32.461,17.625 32.727,17.774C32.992,17.922 33.301,17.997 33.652,17.997C33.992,17.997 34.279,17.934 34.514,17.809C34.748,17.68 34.922,17.512 35.035,17.305H36.301C36.207,17.649 36.039,17.952 35.797,18.213C35.555,18.471 35.248,18.672 34.877,18.817C34.51,18.958 34.098,19.028 33.641,19.028ZM35.152,15.161C35.152,14.836 35.086,14.547 34.953,14.293C34.824,14.04 34.641,13.842 34.402,13.702C34.164,13.557 33.887,13.485 33.57,13.485C33.246,13.485 32.957,13.561 32.703,13.713C32.453,13.862 32.256,14.065 32.111,14.323C31.967,14.577 31.885,14.856 31.865,15.161H35.152Z"
android:fillColor="#FF7490"/>
</vector>
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Loading