Skip to content

Commit 076c0f9

Browse files
[쇼핑 주문 1, 2 단계] 모찌 미션 제출합니다 (#111)
* feat: set up the project * docs: 기능 요구 사항 작성 * build: shimmer 의존성 추가 * feat: 상품 목록 shimmer layout 구현 * feat: recyclerView Item에 상품 목록 shimmer 추가 * feat: recyclerView LoadingStateProduct의 ViewHolder, Adapter 적용 * feat: 카탈로그 화면에 Shimmer UI 추가 * feat: 장바구니 화면 로딩 UI 추가 - 장바구니 화면에 Shimmer Effect를 활용한 로딩 UI를 추가했습니다. - 로딩 중에는 ShimmerFrameLayout이 화면에 표시되고, 로딩이 완료되면 사라집니다. - CatalogViewModel과 CartViewModel에 2초의 임의 딜레이를 추가하여 로딩 효과를 확인할 수 있도록 했습니다. * feat: 사용자 인증 정보 저장 * build: retrofit 의존성 추가 * feat: 장바구니 아이템 DTO 추가 * feat: Product service, Product retrofit 구현 * feat: RemoteCatalogProductRepositoryImpl 구현 및 적용 - RemoteCatalogProductRepositoryImpl 추가 - CatalogProductRepository에 getProductsByPage 추가 - CatalogViewModel에 RemoteCatalogProductRepositoryImpl 의존성 추가 - LocalDummyCatalogProductRepositoryImpl 주석 처리 - network_security_config에 서버 도메인 추가 - ShoppingApplication에서 ID 변경 * feat: Retrofit 서비스 싱글톤으로 변경 및 로깅 인터셉터 추가 * refactor: 잘못 사용한 DTO import 변경 * feat: 서버에서 상품 목록 불러오는 기능 구현 * feat: 서버에서 상세 페이지 조회 구현 * feat: detailActivty에서 최근 본 상품 조회 구현 * feat: CartItem service, retrofit 구현 * refactor: CartProductRepository를 Remote로 변경 * feat: 장바구니에 상품 추가 후, cartItemId 콜백 받도록 구현 * feat: 장바구니에 담긴 상품 수량 표시 및 상품 상세 화면으로 이동 시 상품 정보 전달 기능 개선 - CatalogViewModel에서 장바구니에 담긴 상품의 cartItemId를 ProductUiModel에 추가 - DetailViewModel에서 상품 정보를 productId 대신 ProductUiModel 객체로 받도록 수정 - DetailViewModel에서 장바구니에 상품 추가 시 이미 담겨있는 상품이면 수량 업데이트, 아니면 새로 추가 - DetailActivity에서 DetailViewModelFactory에 productId 대신 ProductUiModel 객체 전달 - CatalogActivity에서 상품 클릭 시 DetailActivity로 productId 대신 ProductUiModel 객체 전달 - 최근 본 상품 클릭 시 DetailActivity로 productId 대신 ProductUiModel 객체 전달 * feat: 제품 상세 페이지에서 장바구니에 없는 상품을 추가하면, 서버에 연동되는 로직 구현 * feat: 상품 목록 페이지 수량 증가 감소 버튼 로직 구현 * fix: 상품 수량 1개일 때 장바구니에서 상품 삭제되도록 수정 * feat: 상품 수량이 0인 경우 카트에 상품 추가, 0이 아닌 경우 수량 변경 기능 추가 * refactor: 불필요한 레포지토리 및 객체 코드 제거 - CartProductRepositoryImpl 삭제 - HttpCatalogProductRepositoryImpl 삭제 - LocalDummyCatalogProductRepositoryImpl 삭제 - CartProductRepository의 getAllProductsSize 메서드 제거 - CartProductRepository의 getProductQuantity 메서드 제거 - RemoteCartProductRepositoryImpl의 불필요한 Log.d 및 println 제거 * refactor: 불필요한 레포지토리 및 객체 코드 제거 - CartProductRepositoryImpl 삭제 - HttpCatalogProductRepositoryImpl 삭제 - LocalDummyCatalogProductRepositoryImpl 삭제 - CartProductRepository의 getAllProductsSize 메서드 제거 - CartProductRepository의 getProductQuantity 메서드 제거 - RemoteCartProductRepositoryImpl의 불필요한 Log.d 및 println 제거 * docs: 2단계 기능 요구 사항 작성 * feat: 주문 화면 레이아웃 구현 * feat: 장바구니 상품 추천 fragment layout 구현 * feat: cart selection fragment 추가 * fix: CartSelectionFragment 뷰 바인딩 오류 수정 및 레이아웃 조정 * feat: 장바구니 상품 개별 선택 기능 추가 * feat: cartActity에 cartViewModel 추가 * feat: 장바구니 전체 금액, 총 개수 표시 기능 추가 * fix: 장바구니 상품 선택 기능 및 총 금액 계산 기능 수정 * feat: 장바구니에 담긴 상품이 없을 때 추천 상품 화면 표시 기능 추가 * feat: 추천상품 받아올 수 있는 로직 구현 * feat: 장바구니 추천 상품 UI 적용 * fix: 카트에 상품 목록이 없을 때, 상품 추천이 뜨도록 추천 * docs: 코니 요구 사항 일부 반영 * refactor: ShoppingApplication에 thread { DevMockServer.start() }와 사용하지 않는 파일 삭제 * refactor: 장바구니 추천 상품에서 리사이클러뷰 4방향 다 제약 조건 설정 * feat: local.properties에 아이디와 비밀번호를 저장하도록 로직 수정 * fix: 최근 상품 목록이 0개인 경우, 최근 본 상품 목록 뷰가 보이지 않게 하기 * refactor: 하드 코딩된 값이 아닌, BuildConfig에서 아이디와 비밀번호 불러오도록 로직 수정 * refactor: CatalogViewModel에서 catalogProduct, quantity 사용하지 않는 값 삭제 * refactor: CatalogViewModel에서 interface에서 remoteCatalogProductRepositoryImpl 실제 구현체 주입 * refactor: baseUrl 노출하지 않도록 수정 * fix: 장바구니 개수 받아오는 api의 HTTP method 변경 * refactor: cartItemCount를 관찰하여, fragment 변경되도록 로직 변경 * refactor: cartItemCount 초기값 상수화 * refactor: activity 함수 분리 * refactor: CartRecommendationFragment에서 사용하지 않는 함수와 Log 삭제 * refactor: 추천 리스트 adapter에서 사용하지 않는 함수는 override 하지 않도록 interface에서 Unit 처리 * refactor: log 삭제 * feat: 상품 목록에서 장바구니에 제품을 담으면, 장바구니 아이템 개수를 앱바에 노출 * feat: 장바구니 추천상품에서 상품을 담을 수 있음 * fix: 상품의 개수에 따라 전체 가격 변동 하도록 수정 * chore: 폴더 구조 변경 * fix: 장바구니 페이지에서 추천 상품을 장바구니에 담은 개수가 1개 초과했을 때, 발생하는 문제 해결 * refactor: 상수화 * refactor: Repository와 DataSource로 분리 * refactor: 상수화 * feat: 장바구니 상품 체크 박스 구현 * refactor: badge_background.xml에서 하드코딩된 색상값을 컬러 리소스로 변경 * refactor: fragment에서 View가 inflate된 후, observeData를 구독하도록 수정 * refactor: 주석 삭제 * refactor: Ktlint 적용 * docs: Readme 업데이트 --------- Co-authored-by: ijh1298 <[email protected]> Co-authored-by: Hwnag ChaeWon <[email protected]>
1 parent fbcb7aa commit 076c0f9

File tree

129 files changed

+5636
-44
lines changed

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

129 files changed

+5636
-44
lines changed

README.md

Lines changed: 56 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1 +1,56 @@
1-
# android-shopping-order
1+
# android-shopping-order
2+
3+
### 1,2단계 기능 요구 사항
4+
- 스켈레톤 UI 노출
5+
- [x] 상품 목록
6+
- [x] 장바구니
7+
- 서버 연동
8+
- [x] 장바구니 아이템
9+
- [ ] 주문
10+
- [x] 상품
11+
- [x] 사용자 인증 정보 저장
12+
- [x] 장바구니 화면에서 특정 상품만 골라 주문하기 버튼을 누를 수 있다.
13+
- [x] 별도의 화면에서 상품 추천 알고리즘으로 사용자에게 적절한 상품을 추천해준다. (쿠팡 UX 참고)
14+
- [x] 상품 추천 알고리즘은 최근 본 상품 카테고리를 기반으로 최대 10개 노출한다.
15+
- [x] 예를 들어 가장 최근에 본 상품이 fashion 카테고리라면, fashion 상품 10개 노출
16+
- [x] 해당 카테고리 상품이 10개 미만이라면 해당하는 개수만큼만 노출
17+
- [x] 장바구니에 이미 추가된 상품이라면 미노출
18+
- [x] 추천된 상품을 해당 화면에서 바로 추가하여 같이 주문할 수 있다.
19+
20+
### 코니 요구사항
21+
- [x] 리드미 작성, PR 작성 잘하기
22+
23+
상품 목록 화면 구현
24+
- [x] 최근 본 상품이 0개인 경우, 최근 본 상품 목록 삭제
25+
26+
Cart 화면 구현
27+
- [x] 추천된 상품을 해당 화면에서 바로 추가하여 같이 주문할 수 있다. 구현
28+
- [x] 총 가격은 변하는데 왜 체크 박스는 변하지 않는 기능 수정
29+
30+
- [x] ShoppingApplication에 thread { DevMockServer.start() } 삭제
31+
- [x] ShoppingApplication에서 정말 SharedPreferences에 값을 저장하고 있는 지 확인 + SharedPreference에서 어떻게 동작하는 지 확인
32+
- [x] CatalogViewModel에서 catalogProduct, quantity 사용하지 않는 값 삭제
33+
- [x] CatalogViewModel에서 interface에서 remoteCatalogProductRepositoryImpl 실제 구현체 주입 -> 수정
34+
- [x] 최근 상품 목록이 0개인 경우, 최근 본 상품 목록 뷰가 보이지 않게 하기
35+
- [x] baseUrl 노출하지 않도록 수정
36+
- [x] 사용자 정보 local.properties에 저장 -> BuildConfig로 갖고 오게 끔 구현
37+
- [ ] 레벨업 부분 ) 인증에 필요한 key은 secerets에 저장
38+
- CartActivity에서 생각해야 할 부분
39+
- [x] CartActivity에서 hasHandledTotalCount가 정말 필요한 로직일지 생각
40+
- [x] View에서 totalCount가 몇인지에 따라서 어떤한 fragment를 commit할지 아닌 ViewModel에서 어떠한 상태를 두고 그 상태에 따라서 화면을 이동하는 것
41+
- [x] View까지 와서 단순 ViewModel의 함수를 호출하는 observe를 해야하는 지 생각 -> ViewModel의 일은 스스로 하게 변
42+
- CartRecommendationFragment
43+
- [x] 로그 삭제
44+
- [x] 이런 식으로 Unit으로 처리를 하게 될 함수가 필수 overide 라면 ProductActionListener 에서 저 함수가 필수가 아니었던 게 아닌지 생각
45+
- [x] collect는 viewLifecycleOwner 따라가게끔 해주셨는데, 데이터 바인딩에 사용될 lifecycleOwner 는 this 를 넘기고 있음
46+
- CartSelectionFragment
47+
- [x] 아직 View가 만들어지기 전에 view에 필요한 데이터를 미리 observe를 하고 adpater를 세팅할 필요한지
48+
- [x] onCreateView에서는 어떠한 동작을 해야 하고 어떠한 단계인지 생각
49+
- [x] ViewModelStoreOwner를 Activity로 잡은 점
50+
- [x] Application이 왜 필요로 하는가?
51+
- [x] Enabled 된 것이랑 updateCartItems 를 호출하는 상관관계 -> 전반적으로 view까지 observe가 되어야 했던 값들일까 하는 의문
52+
- [x] 데이터 바인딩 적용
53+
- [x] updateCartItems()가 view에서 처리해야하는 지 생각
54+
- CartViewModel
55+
- [x] totalCount가 -1 라는 것은 논리적으로 읽을 수 없음 -> 상수화
56+
----- https://github.com/woowacourse/android-shopping-order/pull/111#discussion_r2113932605 여기 부터 적용 다시

app/build.gradle.kts

Lines changed: 35 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,11 @@
1+
import java.util.Properties
2+
13
plugins {
24
alias(libs.plugins.android.application)
35
alias(libs.plugins.android.junit5)
46
alias(libs.plugins.kotlin.android)
7+
id("kotlin-kapt")
8+
id("kotlin-parcelize")
59
}
610

711
android {
@@ -16,7 +20,18 @@ android {
1620
versionName = "1.0"
1721

1822
testInstrumentationRunner = "androidx.test.runner.AndroidJUnitRunner"
19-
testInstrumentationRunnerArguments["runnerBuilder"] = "de.mannodermaus.junit5.AndroidJUnit5Builder"
23+
testInstrumentationRunnerArguments["runnerBuilder"] =
24+
"de.mannodermaus.junit5.AndroidJUnit5Builder"
25+
26+
val localProperties = Properties()
27+
val localPropertiesFile = rootProject.file("local.properties")
28+
if (localPropertiesFile.exists()) {
29+
localProperties.load(localPropertiesFile.inputStream())
30+
}
31+
32+
buildConfigField("String", "USER_ID", "\"${localProperties.getProperty("USER_ID")}\"")
33+
buildConfigField("String", "USER_PASSWORD", "\"${localProperties.getProperty("USER_PASSWORD")}\"")
34+
buildConfigField("String", "BASE_URL", "\"${localProperties.getProperty("BASE_URL")}\"")
2035
}
2136

2237
buildTypes {
@@ -41,17 +56,36 @@ android {
4156
excludes += "win32-x86*/**"
4257
}
4358
}
59+
buildFeatures {
60+
dataBinding = true
61+
buildConfig = true
62+
}
4463
}
4564

4665
dependencies {
66+
implementation(libs.glide)
67+
implementation(libs.androidx.room.runtime)
68+
kapt(libs.androidx.room.compiler)
4769
implementation(libs.androidx.activity.ktx)
4870
implementation(libs.androidx.appcompat)
4971
implementation(libs.androidx.constraintlayout)
5072
implementation(libs.androidx.core.ktx)
5173
implementation(libs.google.material)
74+
implementation(libs.androidx.activity)
75+
implementation(libs.okhttp)
76+
implementation(libs.gson)
77+
implementation(libs.mockwebserver)
78+
implementation(libs.shimmer)
79+
implementation(libs.retrofit)
80+
implementation(libs.converter.gson)
81+
implementation(libs.logging.interceptor)
82+
implementation(libs.androidx.fragment.ktx)
83+
84+
testImplementation(libs.androidx.core.testing)
5285
testImplementation(libs.assertj.core)
5386
testImplementation(libs.junit.jupiter)
5487
testImplementation(libs.kotest.runner.junit5)
88+
5589
androidTestImplementation(libs.androidx.espresso.core)
5690
androidTestImplementation(libs.androidx.test.ext.junit)
5791
androidTestImplementation(libs.androidx.test.runner)

app/src/main/AndroidManifest.xml

Lines changed: 13 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,12 @@
22
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
33
xmlns:tools="http://schemas.android.com/tools">
44

5+
<uses-permission android:name="android.permission.INTERNET" />
6+
57
<application
8+
android:networkSecurityConfig="@xml/network_security_config"
9+
android:usesCleartextTraffic="true"
10+
android:name=".ShoppingApplication"
611
android:allowBackup="true"
712
android:dataExtractionRules="@xml/data_extraction_rules"
813
android:fullBackupContent="@xml/backup_rules"
@@ -13,7 +18,13 @@
1318
android:theme="@style/Theme.Shopping"
1419
tools:targetApi="31">
1520
<activity
16-
android:name=".MainActivity"
21+
android:name=".cart.CartActivity"
22+
android:exported="false" />
23+
<activity
24+
android:name=".product.detail.DetailActivity"
25+
android:exported="false" />
26+
<activity
27+
android:name=".product.catalog.CatalogActivity"
1728
android:exported="true">
1829
<intent-filter>
1930
<action android:name="android.intent.action.MAIN" />
@@ -23,4 +34,4 @@
2334
</activity>
2435
</application>
2536

26-
</manifest>
37+
</manifest>

app/src/main/java/woowacourse/shopping/MainActivity.kt

Lines changed: 0 additions & 20 deletions
This file was deleted.
Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,14 @@
1+
package woowacourse.shopping
2+
3+
import android.app.Application
4+
import woowacourse.shopping.data.database.ShoppingDatabase
5+
6+
class ShoppingApplication : Application() {
7+
lateinit var database: ShoppingDatabase
8+
private set
9+
10+
override fun onCreate() {
11+
super.onCreate()
12+
database = ShoppingDatabase.getInstance(applicationContext)
13+
}
14+
}
Lines changed: 79 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,79 @@
1+
package woowacourse.shopping.cart
2+
3+
import android.content.Context
4+
import android.content.Intent
5+
import android.os.Bundle
6+
import androidx.activity.enableEdgeToEdge
7+
import androidx.appcompat.app.AppCompatActivity
8+
import androidx.core.view.ViewCompat
9+
import androidx.core.view.WindowInsetsCompat
10+
import androidx.databinding.DataBindingUtil
11+
import androidx.fragment.app.commit
12+
import androidx.lifecycle.ViewModelProvider
13+
import woowacourse.shopping.R
14+
import woowacourse.shopping.cart.recoomendation.RecommendationFragment
15+
import woowacourse.shopping.cart.selection.SelectionFragment
16+
import woowacourse.shopping.databinding.ActivityCartBinding
17+
18+
class CartActivity : AppCompatActivity() {
19+
private val binding: ActivityCartBinding by lazy {
20+
DataBindingUtil.setContentView(this, R.layout.activity_cart)
21+
}
22+
private val viewModel: CartViewModel by lazy {
23+
ViewModelProvider(
24+
this,
25+
CartViewModelFactory(),
26+
)[CartViewModel::class.java]
27+
}
28+
29+
override fun onCreate(savedInstanceState: Bundle?) {
30+
super.onCreate(savedInstanceState)
31+
enableEdgeToEdge()
32+
33+
initView()
34+
initActionBar()
35+
initDataBinding()
36+
observeData()
37+
}
38+
39+
private fun initView() {
40+
ViewCompat.setOnApplyWindowInsetsListener(binding.root) { v, insets ->
41+
val systemBars = insets.getInsets(WindowInsetsCompat.Type.systemBars())
42+
v.setPadding(systemBars.left, systemBars.top, systemBars.right, systemBars.bottom)
43+
insets
44+
}
45+
}
46+
47+
private fun initActionBar() {
48+
supportActionBar?.setDisplayHomeAsUpEnabled(true)
49+
supportActionBar?.title = getString(R.string.text_cart_action_bar)
50+
}
51+
52+
override fun onSupportNavigateUp(): Boolean {
53+
finish()
54+
return super.onSupportNavigateUp()
55+
}
56+
57+
private fun initDataBinding() {
58+
binding.vm = viewModel
59+
binding.lifecycleOwner = this
60+
}
61+
62+
private fun observeData() {
63+
viewModel.totalPurchaseCount.observe(this) { totalPurchaseCount ->
64+
val fragment =
65+
when (totalPurchaseCount) {
66+
0 -> RecommendationFragment()
67+
else -> SelectionFragment()
68+
}
69+
supportFragmentManager.commit {
70+
setReorderingAllowed(true)
71+
replace(R.id.fragment_container_cart_selection, fragment)
72+
}
73+
}
74+
}
75+
76+
companion object {
77+
fun newIntent(context: Context): Intent = Intent(context, CartActivity::class.java)
78+
}
79+
}
Lines changed: 64 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,64 @@
1+
package woowacourse.shopping.cart
2+
3+
import android.view.ViewGroup
4+
import androidx.recyclerview.widget.RecyclerView
5+
import woowacourse.shopping.cart.CartItem.PaginationButtonItem
6+
import woowacourse.shopping.cart.CartItem.ProductItem
7+
import woowacourse.shopping.product.catalog.ProductUiModel
8+
import woowacourse.shopping.product.catalog.QuantityControlListener
9+
10+
class CartAdapter(
11+
cartItems: List<CartItem>,
12+
private val onDeleteProductClick: DeleteProductClickListener,
13+
private val onPaginationButtonClick: PaginationButtonClickListener,
14+
private val quantityControlListener: QuantityControlListener,
15+
private val onCheckClick: CheckClickListener,
16+
) : RecyclerView.Adapter<RecyclerView.ViewHolder>() {
17+
private val cartItems: MutableList<CartItem> = cartItems.toMutableList()
18+
19+
fun setCartItems(cartProducts: List<CartItem>) {
20+
notifyItemRangeRemoved(0, cartItems.size)
21+
cartItems.clear()
22+
cartItems.addAll(cartProducts)
23+
notifyItemRangeInserted(0, cartItems.size)
24+
}
25+
26+
fun setCartItem(product: ProductUiModel) {
27+
val index = cartItems.filterIsInstance<ProductItem>().indexOfFirst { it.productItem.id == product.id }
28+
cartItems[index] = ProductItem(product)
29+
notifyItemChanged(index)
30+
}
31+
32+
override fun onCreateViewHolder(
33+
parent: ViewGroup,
34+
viewType: Int,
35+
): RecyclerView.ViewHolder =
36+
if (viewType == CART_PRODUCT) {
37+
CartViewHolder.from(parent, onDeleteProductClick, quantityControlListener, onCheckClick)
38+
} else {
39+
PaginationButtonViewHolder.from(parent, onPaginationButtonClick)
40+
}
41+
42+
override fun onBindViewHolder(
43+
holder: RecyclerView.ViewHolder,
44+
position: Int,
45+
) {
46+
when (holder) {
47+
is CartViewHolder -> holder.bind((cartItems[position] as ProductItem).productItem)
48+
is PaginationButtonViewHolder -> holder.bind(cartItems[position] as PaginationButtonItem)
49+
}
50+
}
51+
52+
override fun getItemViewType(position: Int): Int =
53+
when (cartItems[position]) {
54+
is PaginationButtonItem -> PAGINATION_BUTTON
55+
is ProductItem -> CART_PRODUCT
56+
}
57+
58+
override fun getItemCount(): Int = cartItems.size
59+
60+
companion object {
61+
private const val CART_PRODUCT = 1
62+
private const val PAGINATION_BUTTON = 2
63+
}
64+
}
Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,15 @@
1+
package woowacourse.shopping.cart
2+
3+
import woowacourse.shopping.product.catalog.ProductUiModel
4+
5+
sealed class CartItem {
6+
data class ProductItem(
7+
val productItem: ProductUiModel,
8+
) : CartItem()
9+
10+
data class PaginationButtonItem(
11+
val page: Int,
12+
val isNextButtonEnabled: Boolean,
13+
val isPrevButtonEnabled: Boolean,
14+
) : CartItem()
15+
}
Lines changed: 34 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,34 @@
1+
package woowacourse.shopping.cart
2+
3+
import android.view.LayoutInflater
4+
import android.view.ViewGroup
5+
import androidx.recyclerview.widget.RecyclerView
6+
import woowacourse.shopping.databinding.CartItemBinding
7+
import woowacourse.shopping.product.catalog.ProductUiModel
8+
import woowacourse.shopping.product.catalog.QuantityControlListener
9+
10+
class CartViewHolder(
11+
private val binding: CartItemBinding,
12+
) : RecyclerView.ViewHolder(binding.root) {
13+
fun bind(cartProduct: ProductUiModel) {
14+
binding.cartProduct = cartProduct
15+
binding.checkboxSelection.isChecked = cartProduct.isChecked
16+
}
17+
18+
companion object {
19+
fun from(
20+
parent: ViewGroup,
21+
onDeleteProductClick: DeleteProductClickListener,
22+
quantityControlListener: QuantityControlListener,
23+
onCheckClick: CheckClickListener,
24+
): CartViewHolder {
25+
val inflater = LayoutInflater.from(parent.context)
26+
val binding = CartItemBinding.inflate(inflater, parent, false)
27+
binding.clickListener = onDeleteProductClick
28+
binding.checkClickListener = onCheckClick
29+
binding.layoutQuantityControlBar.quantityControlListener = quantityControlListener
30+
31+
return CartViewHolder(binding)
32+
}
33+
}
34+
}

0 commit comments

Comments
 (0)