Skip to content
Merged
Show file tree
Hide file tree
Changes from 46 commits
Commits
Show all changes
65 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
e3eb786
[PC-1415] MatchingScreenTest 테스트 코드 수정
kkh725 Nov 27, 2025
bf07166
[PC-1415] MatchViewModel 테스트 코드 수정
kkh725 Nov 27, 2025
ff2c859
[PC-1415] contentDescription 수정
kkh725 Nov 27, 2025
bfc478b
[PC-1415] MatchingDetail 테스트 코드 수정
kkh725 Nov 27, 2025
444444d
[PC-1415] MatchingScreenTest 코드 수정
kkh725 Nov 27, 2025
d11a725
[PC-1415] test를 위한 뒤로가기 버튼 텍스트수정
kkh725 Nov 27, 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 formatIsoToCustomFormat(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
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()
}
Comment on lines 47 to 62
Copy link
Member

Choose a reason for hiding this comment

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

p2) 내부 구현이 길어서 회귀가 발생할 수 있을 것 같아요. 이후 리팩토링이나 다른 사람이 봤을 떄 이해하기 쉽도록 테스트 코드가 작성되어야 할 것 같아요.

Copy link
Collaborator Author

Choose a reason for hiding this comment

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

이부분은 matchInfoList 가 3개로 나누어져있었는데, 서버쪽에서 한개의 메서드로 변경해주시기로 하였습니다!

Copy link
Member

Choose a reason for hiding this comment

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

suspendRunCatching {
            listOf(matchingDataSource.getMatchInfo().toDomain(MatchType.BASIC))
        }.getOrElse { throwable ->
            if (throwable is HttpResponseException && throwable.status == HttpResponseStatus.NotFound) {
                emptyList()
            } else throw throwable
        }

이 부분에 runCatching으로 감싸주신 이유가 궁금해요.

viewModel에서 에러 핸들링 하면 안되나요 ? 아래 toMeMatchList랑 fromMeMatchList를 호출하는 과정에서 똑같은 예외 코드가 떨어졌을 때 에러 핸들링이 다른 것일까요?!

Copy link
Collaborator Author

@kkh725 kkh725 Nov 25, 2025

Choose a reason for hiding this comment

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

일단 MatchType.BASIC 으로 들어오는 기본 매치 정보( 오후 10시마다 갱신 )의 예외는 HttpResponseStatus.NotFound 예외만 정상작동 처리하고, (사용자를 찾을수없음) 나머지는 예외처리 하는것으로 알고 있습니다.

하지만 toMe, fromMe는 위 같이 http코드로 예외처리를 하지않고, 없다면 그냥 빈 리스트를 던져주고 있습니다! 따라서 3개의 api가 예외처리방식이 다르기때문에 요렇게 작성했습니당.

Copy link
Collaborator Author

Choose a reason for hiding this comment

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

뷰모델에서 각각 핸들링하는 방식도 생각해봤는데, 3개의 매치정보조회 api를 통합해서 올려보내는게 깔끔해보여서 작성한 것도 있어요. 고민이 만히 되었습니다. 에러케이스도 처리방식이 다르다보니..

Copy link
Member

Choose a reason for hiding this comment

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

일단 MatchType.BASIC 으로 들어오는 기본 매치 정보( 오후 10시마다 갱신 )의 예외는 HttpResponseStatus.NotFound 예외만 정상작동 처리하고, (사용자를 찾을수없음) 나머지는 예외처리 하는것으로 알고 있습니다.

하지만 toMe, fromMe는 위 같이 http코드로 예외처리를 하지않고, 없다면 그냥 빈 리스트를 던져주고 있습니다! 따라서 3개의 api가 예외처리방식이 다르기때문에 요렇게 작성했습니당.

toMe랑 fromMe가 똑같은 에러를 던지지 않는다면 viewModel에서 똑같은 조건 그대로 처리해도 로직이 같지 않나 싶어요~!


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 +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
Copy link
Member

Choose a reason for hiding this comment

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

p2) 추가된 케이스에 대한 Preview가 추가되어야 할 것 같아요.

Copy link
Collaborator Author

Choose a reason for hiding this comment

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

추가완료 했습니다. 1d54142

Original file line number Diff line number Diff line change
Expand Up @@ -83,6 +83,58 @@ 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,
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 +240,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(
title: String,
remainTime: String,
contentColor: Color,
closeButtonEnabled: Boolean,
onCloseClick: () -> Unit,
modifier: Modifier = Modifier,
) {
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)
)
}
}

Comment on lines +282 to +298
Copy link
Member

Choose a reason for hiding this comment

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

👍👍

@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>
Copy link
Member

Choose a reason for hiding this comment

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

이거 Png 형태인데 간단한 이미지인 것 같아서요. Gradient 로는 표현할 수 없었을까요? 여러가지 디바이스에서 다 호환되나요 ?

Copy link
Collaborator Author

@kkh725 kkh725 Nov 25, 2025

Choose a reason for hiding this comment

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

https://discord.com/channels/1309894065139220620/1442697833533800459

생각보다 공수가 들것같은 분위기..네요
디바이스들 호환은 테스트 해보고 다시 코멘트 달겠습니다!

Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Loading