Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
28 commits
Select commit Hold shift + click to select a range
58d6710
Refactor : 하드코딩 문자열 리소르로 관리
oyj7677 Aug 23, 2024
1447981
Feat : 카드 저장소 구현
oyj7677 Aug 23, 2024
920a2e9
Feat : 카드 목록 업데이트 제공 코드 및 카드 목록 스크린 생성
oyj7677 Aug 23, 2024
bdf1c1e
Feat : 카드 리스트 탑바 생성
oyj7677 Aug 23, 2024
c055005
Feat : 등록 카드 이미지 컴포넌트 구현
oyj7677 Aug 23, 2024
0e0e1ef
Rename : 등록된 카드 이미지 관련 패키지 이동
oyj7677 Aug 23, 2024
c1e79cb
Feat : 카드 등록 이미지 및 카드 등록 코멘트 컴포넌트 생성
oyj7677 Aug 23, 2024
6f9306f
Feat : 카드 리스트 노출 UI 구현
oyj7677 Aug 23, 2024
d2348fd
Test : 카드 리스트 스크린 테스트 구현
oyj7677 Aug 23, 2024
f308088
Feat : 카드 등록 엑티비티 이동 구현
oyj7677 Aug 23, 2024
e6b1b92
Chore : Step 2 - 페이먼츠(카드 목록) 관련 README.md업데이트
oyj7677 Aug 23, 2024
c6281b0
Chore : README.md 기능 목록 추가
oyj7677 Aug 25, 2024
966b37d
Feat : 카드 등록 업데이트 구현
oyj7677 Aug 25, 2024
09c596d
Rename : CardList 클래스 이름 변경
oyj7677 Aug 25, 2024
fc6de3c
Feat : 등록 카드에 따른 카드 목록 화면 변경 구현
oyj7677 Aug 25, 2024
a033004
Style : 코드 오토 컨벤션
oyj7677 Aug 25, 2024
711e53b
Style : 네이밍 오류 수정
oyj7677 Aug 25, 2024
6f7fbd9
Refactor : 불필요한 파라미터 및 로직 제거
oyj7677 Aug 26, 2024
d20d0e2
Fix : 카드 추가 OutlinedTextField의 label 및 Placeholder 노출
oyj7677 Aug 26, 2024
97ca6a1
Rename : 패키지 변경 및 네이밍 수정
oyj7677 Aug 26, 2024
70b7d52
Refactor : CardListScreen 상태 호이스팅 적용
oyj7677 Aug 26, 2024
1f21996
Chore : 사용하지 않는 코드 삭제
oyj7677 Aug 26, 2024
ebaac53
Refactor : 빈 카드 이미지 버튼 클릭 영역 수정
oyj7677 Aug 26, 2024
ccb10ab
Refactor : CardListScreenEmpty 코멘트 컴포넌트 수정
oyj7677 Aug 26, 2024
d7cf0c0
Remove : CardRegistrationComment 컴포넌트 삭제
oyj7677 Aug 26, 2024
722c08a
Refactor : CardListViewModel의 init() 블록 삭제
oyj7677 Aug 26, 2024
89d2f55
Refactor : PaymentCard 오버로딩 함수 생성
oyj7677 Aug 26, 2024
2840f8e
Remove : CardImage 컴포넌트 삭제
oyj7677 Aug 26, 2024
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
11 changes: 10 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
@@ -1 +1,10 @@
# android-payments
# Step 2 - 페이먼츠(카드 목록)

## 기능 목록
- [O] 카드 목록 화면 구현
- [O] 카드 목록 화면 -> 카드 추가 화면 연결
- [] 새로운 카드가 추가되었을 때 카드 목록이 업데이트 되어야 한다.
- [O] 카드 목록이 비어있다면 카드 등록 코멘트 Text 노출
- [] 카드 목록에 카드가 한 개 있을 경우 카드 추가 UI는 목록 하단에 노출한다
- [] 카드 목록에 카드가 두 개 이상 있을 경우 카드 추가 UI는 상단바에 노출된다.
- [] 카드 목록 데이터를 보관하기 위해 ViewModel과 StateFlow를 활용한다
Original file line number Diff line number Diff line change
@@ -0,0 +1,150 @@
package nextstep.payments.screen.card.list

import androidx.compose.ui.test.junit4.createComposeRule
import androidx.compose.ui.test.onAllNodesWithTag
import androidx.compose.ui.test.onNodeWithTag
import androidx.compose.ui.test.onNodeWithText
import nextstep.payments.data.BcCard
import nextstep.payments.data.Card
import nextstep.payments.data.PaymentCardsRepository
import nextstep.payments.data.RegisteredCreditCards
import nextstep.payments.ui.card.list.CardListScreen
import org.junit.Before
import org.junit.Rule
import org.junit.Test

class RegisteredCreditCardsScreenTest {

@get:Rule
val composeRule = createComposeRule()

@Before
fun setUp() {
PaymentCardsRepository.removeAllCard()
}

@Test
fun 스크린_상단에_Payments_타이틀이_노출된다() {
// when : cardListScreen을 setContent 한다.
composeRule
.setContent {
CardListScreen(
registeredCreditCards = RegisteredCreditCards(mutableListOf()),
onAddCard = {}
)
}

// then : Payments 타이틀에 노출되어야한다.
composeRule
.onNodeWithText("Payments")
.assertExists()
}

@Test
fun 등록된_카드가_존재할_경우_해당_카드_수만큼_카드_이미지를_노출시키다() {
// given : 두 개의 카드를 등록한다
val registeredCreditCards = RegisteredCreditCards(
mutableListOf(
Card(
cardNumber = "1234-5678-1234-5678",
ownerName = "홍길동",
expiredDate = "12/24",
password = "123",
cardCompany = BcCard
),
Card(
cardNumber = "1234-5678-1234-5628",
ownerName = "홍길동",
expiredDate = "12/24",
password = "123",
cardCompany = BcCard
)
)
)

// when : 화면을 렌더링한다.
composeRule.setContent {
CardListScreen(
registeredCreditCards = registeredCreditCards
)
}
val actual = composeRule
.onAllNodesWithTag("cardImage")
.fetchSemanticsNodes().size

// then : 카드 이미지 두개가 노출되어야 한다.
assert(actual == 2)
}

@Test
fun 등록된_카드가_존재하지_않을_경우_해당_카드_이미지를_노출하지_않는다() {
// when : 화면을 렌더링한다.
composeRule.setContent {
CardListScreen(
registeredCreditCards = RegisteredCreditCards(mutableListOf())
)
}

// then : 카드 이미지가 노출되지 않는다.
composeRule
.onNodeWithTag("cardImage")
.assertDoesNotExist()
}

@Test
fun 등록된_카드가_존재하지_않을_경우_카드_추가_멘트가_노출된다() {
// given : 카드 등록이 되어있지 않다.


// when : 화면을 렌더링한다.
// cardImage
composeRule.setContent {
CardListScreen(
registeredCreditCards = RegisteredCreditCards(mutableListOf())
)
}

// then : 카드 추가 멘트가 노출된다.
composeRule
.onNodeWithTag("textComment")
.assertExists()
}

@Test
fun 등록된_카드가_존재할_경우_카드_추가_멘트가_노출되자_않는다() {
// given : 카드 등록이 되어있다.
PaymentCardsRepository.addCard(
Card(
cardNumber = "1234-5678-1234-5628",
ownerName = "홍길동",
expiredDate = "12/24",
password = "123",
cardCompany = BcCard
)
)
val registeredCreditCards = RegisteredCreditCards(
mutableListOf(
Card(
cardNumber = "1234-5678-1234-5628",
ownerName = "홍길동",
expiredDate = "12/24",
password = "123",
cardCompany = BcCard
)
)
)


// when : 화면을 렌더링한다.
composeRule.setContent {
CardListScreen(
registeredCreditCards = registeredCreditCards
)
}

// then : 카드 추가 멘트가 노출되지 않는다
composeRule
.onNodeWithTag("textComment")
.assertDoesNotExist()
}
}
1 change: 1 addition & 0 deletions app/src/main/AndroidManifest.xml
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,7 @@
<category android:name="android.intent.category.LAUNCHER" />
</intent-filter>
</activity>
<activity android:name=".ui.card.registration.NewCardActivity"/>
</application>

</manifest>
30 changes: 24 additions & 6 deletions app/src/main/java/nextstep/payments/MainActivity.kt
Original file line number Diff line number Diff line change
@@ -1,29 +1,47 @@
package nextstep.payments

import android.content.Intent
import android.os.Bundle
import androidx.activity.ComponentActivity
import androidx.activity.compose.rememberLauncherForActivityResult
import androidx.activity.compose.setContent
import androidx.activity.result.contract.ActivityResultContracts
import androidx.activity.viewModels
import androidx.compose.foundation.layout.fillMaxSize
import androidx.compose.material3.MaterialTheme
import androidx.compose.material3.Surface
import androidx.compose.material3.Text
import androidx.compose.runtime.Composable
import androidx.compose.ui.Modifier
import androidx.compose.ui.tooling.preview.Preview
import nextstep.payments.ui.NewCardScreen
import nextstep.payments.ui.card.list.CardListScreen
import nextstep.payments.ui.card.list.CardListViewModel
import nextstep.payments.ui.card.registration.NewCardActivity
import nextstep.payments.ui.theme.PaymentsTheme

class MainActivity : ComponentActivity() {
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)

val viewModel by viewModels<CardListViewModel>()

setContent {
val launcher =
rememberLauncherForActivityResult(ActivityResultContracts.StartActivityForResult()) {
if (it.resultCode == RESULT_OK) {
viewModel.fetchCards()
}
}

PaymentsTheme {
// A surface container using the 'background' color from the theme
Surface(
modifier = Modifier.fillMaxSize(),
color = MaterialTheme.colorScheme.background
) {
NewCardScreen()
CardListScreen(
viewModel = viewModel,
onAddCard = {
val intent = Intent(this, NewCardActivity::class.java)
launcher.launch(intent)
}
)
}
}
}
Expand Down
17 changes: 15 additions & 2 deletions app/src/main/java/nextstep/payments/NewCardViewModel.kt
Original file line number Diff line number Diff line change
Expand Up @@ -4,8 +4,12 @@ import androidx.lifecycle.ViewModel
import kotlinx.coroutines.flow.MutableStateFlow
import kotlinx.coroutines.flow.StateFlow
import kotlinx.coroutines.flow.asStateFlow
import nextstep.payments.data.Card
import nextstep.payments.data.PaymentCardsRepository

class NewCardViewModel : ViewModel() {
class NewCardViewModel(
private val repository: PaymentCardsRepository = PaymentCardsRepository
) : ViewModel() {

private val _cardNumber = MutableStateFlow("")
val cardNumber: StateFlow<String> = _cardNumber.asStateFlow()
Expand All @@ -19,6 +23,10 @@ class NewCardViewModel : ViewModel() {
private val _password = MutableStateFlow("")
val password: StateFlow<String> = _password.asStateFlow()

private val _cardAdded = MutableStateFlow<Boolean>(false)
val cardAdded: StateFlow<Boolean> = _cardAdded.asStateFlow()


fun setCardNumber(cardNumber: String) {
_cardNumber.value = cardNumber
}
Expand All @@ -34,4 +42,9 @@ class NewCardViewModel : ViewModel() {
fun setPassword(password: String) {
_password.value = password
}
}

fun addCard(card: Card) {
repository.addCard(card)
_cardAdded.value = true
}
Comment on lines +46 to +49
Copy link
Member

Choose a reason for hiding this comment

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

도메인 로직에 너무 많은 신경을 쓰지 않고 학습 목표를 만족하기 위해 잘 구현해주셨어요 👍

}
9 changes: 9 additions & 0 deletions app/src/main/java/nextstep/payments/data/Card.kt
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
package nextstep.payments.data

data class Card(
val cardNumber: String,
val expiredDate: String,
val ownerName: String,
val password: String,
val cardCompany: CardCompany = BcCard
Copy link
Member

Choose a reason for hiding this comment

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

3단계 요구사항을 미리 고려해주셨군요 😮

)
5 changes: 5 additions & 0 deletions app/src/main/java/nextstep/payments/data/CardCompany.kt
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
package nextstep.payments.data

sealed class CardCompany

data object BcCard : CardCompany()
15 changes: 15 additions & 0 deletions app/src/main/java/nextstep/payments/data/PaymentCardsRepository.kt
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
package nextstep.payments.data

object PaymentCardsRepository {

private val _cards = mutableListOf<Card>()
val cards: List<Card> get() = _cards.toList()

fun addCard(card: Card) {
_cards.add(card)
}

fun removeAllCard() {
_cards.clear()
}
}
14 changes: 14 additions & 0 deletions app/src/main/java/nextstep/payments/data/RegisteredCreditCards.kt
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
package nextstep.payments.data

import nextstep.payments.ui.card.CreditCardUiState

data class RegisteredCreditCards(val cardList: List<Card>) {

fun getState(): CreditCardUiState {
return when (cardList.size) {
0 -> CreditCardUiState.Empty
1 -> CreditCardUiState.One(cardList[0])
else -> CreditCardUiState.Many(cardList)
}
}
}
Loading