Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[둘리] 3, 4단계 쇼핑 장바구니 제출합니다. #33

Merged
merged 47 commits into from
May 28, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
47 commits
Select commit Hold shift + click to select a range
6f04e7e
refactor: 함수 간소화
hyemdooly May 15, 2023
e7af72c
refactor: supportActionBar Label 설정 삭제, manifest 이용
hyemdooly May 15, 2023
6977e5f
refactor: test 코드 수정
hyemdooly May 15, 2023
8c85cf2
feat: ProductList 상품 장바구니 추가 뷰 수정
hyemdooly May 16, 2023
723c091
docs: 3단계 요구사항 정리
hyemdooly May 16, 2023
b528d57
feat: 장바구니 버튼 생성
hyemdooly May 16, 2023
fd660c5
feat: ProductDetail 수량 선택 Dialog 구현
hyemdooly May 16, 2023
e9a1b6c
feat: CartActivity 하단 뷰 구현
hyemdooly May 16, 2023
7181e9b
feat: CartActivity Item layout 변경사항 수정
hyemdooly May 16, 2023
27b2a1b
feat: cart badge 생성
hyemdooly May 17, 2023
dd70d3b
feat: ProductListActivity List Count 구현
hyemdooly May 17, 2023
e230169
feat: ProductListActivity AppBar Cart Badge 구현
hyemdooly May 17, 2023
15dc571
feat: ProductDetailActivity 마지막으로 본 상품 startActivity
hyemdooly May 17, 2023
588a1ee
feat: ProductDetailActivity 다른 액티비티 실행 시 Stack 조절
hyemdooly May 17, 2023
dc79515
feat: Cart 아이콘 장바구니 비었으면 Gone으로 수정
hyemdooly May 17, 2023
7d0a2e3
feat: Cart 아이콘 장바구니 비었으면 Gone으로 수정
hyemdooly May 17, 2023
8f145f4
Merge remote-tracking branch 'origin/step4' into step4
hyemdooly May 17, 2023
9370658
feat: ProductListPresenter에서 삭제했던 최근 본 상품 복구
hyemdooly May 18, 2023
0337194
refactor: domain 코드 이동, ktlintformat
hyemdooly May 18, 2023
3e7fc30
feat: CartActivity 상품 개수 업데이트 구현
hyemdooly May 18, 2023
1c419ad
feat: CartActivity, CartSystem 선택 구현 (리팩토링 필수)
hyemdooly May 18, 2023
49e6293
feat: ProductListActivity <-> CartActivity 데이터 동일하게 연동
hyemdooly May 18, 2023
3200e3a
refactor: CartActivity LiveData 활용 리팩터링
hyemdooly May 19, 2023
cbcd2f4
refactor: DataBinding format 수정
hyemdooly May 19, 2023
01b5db9
fix: CartActivity 버그 수정
hyemdooly May 20, 2023
57a2b4d
refactor: ProductListActivity Presenter 리팩터링
hyemdooly May 20, 2023
f6d3645
fix: ProductListActivity Presenter 리팩터링 및 버그 수정
hyemdooly May 20, 2023
b5e1269
refactor: CartPresenterTest 코드 수정에 맞게 리팩터링 및 수정
hyemdooly May 20, 2023
dc3106f
refactor: ProductDetailPresenterTest 리팩터링 및 수정
hyemdooly May 20, 2023
47cb4a5
refactor: ProductListPresenterTest 수정, CartPresenterTest 테스트 통과 안되는 …
hyemdooly May 20, 2023
e23cdba
refactor: formatting
hyemdooly May 20, 2023
65cade1
feat: 4단계 MockServer 구현 및 적용
hyemdooly May 21, 2023
b3e732f
refactor: 강제종료 버그 수정, 패키지 정리
hyemdooly May 21, 2023
6869b1f
fix: 최근 본 상품 반대로 나오는 버그 수정, ProductDetialActivity 다이얼로그 dismiss 추가
hyemdooly May 21, 2023
ebdc9bd
docs: README 업데이트
hyemdooly May 21, 2023
ba1d77f
fix: 갯수 제한 걸리는 버그 수정
hyemdooly May 23, 2023
2797d74
Merge remote-tracking branch 'origin/step4' into step4
hyemdooly May 23, 2023
5cf4f06
refactor: CartProductModel의 isChecked, count variable -> valuable로 변경
hyemdooly May 27, 2023
c291ea5
refactor: ProductModel variable -> valuable로 수정
hyemdooly May 27, 2023
508e204
refactor: backing property 변수 위치 수정
hyemdooly May 27, 2023
b04a4af
refactor: lateinit var -> by lazy로 최대한 수정
hyemdooly May 27, 2023
8d09837
refactor: presenter에서 데이터를 내려주도록 수정
hyemdooly May 27, 2023
a9268da
refactor: AppCompatButton -> Button으로 수정
hyemdooly May 27, 2023
9bac38d
refactor: 요구사항에 없는 내용 삭제
hyemdooly May 27, 2023
261e924
refactor: 테스트코드 로직 최대한 삭제
hyemdooly May 27, 2023
9ec4930
refactor: ProductViewHolder 생성자에서 onClick 등록
hyemdooly May 27, 2023
1227d57
refactor: plus, minus 한계치 수정
hyemdooly May 27, 2023
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
33 changes: 19 additions & 14 deletions README.md
Original file line number Diff line number Diff line change
@@ -1,30 +1,35 @@
# android-shopping-cart
## Domain
### ProductRepository
- [ ] 상품 목록을 가져올 수 있다.
- [x] 상품 목록을 가져올 수 있다.
### CartRepository
- [ ] 장바구니의 상품 목록을 가져올 수 있다.
- [ ] 장바구니에 상품을 추가할 수 있다.
- [ ] 장바구니에 상품을 삭제할 수 있다.
### RecentViewedRepository
- [ ] 사용자가 상품의 상세 정보를 조회하면 목록에 추가한다.
- [ ] 만약 10개가 넘어갔을 경우 가장 오래 된 상품을 삭제한다.
- [ ] 최근 본 상품 목록을 가져올 수 있다.
- [x] 장바구니의 상품 목록을 가져올 수 있다.
- [x] 장바구니에 상품을 추가할 수 있다.
- [x] 장바구니에 상품을 삭제할 수 있다.
### Product
- name : 이름
- imageUrl : 이미지 URL
- price : 가격
### Price
- price : 가격
## View
- [ ] 앱이 종료돼도 최근 본 상품 목록과 장바구니 데이터는 유지돼야 한다.
- [x] 앱이 종료돼도 최근 본 상품 목록과 장바구니 데이터는 유지돼야 한다.
### ProductListActivity
- [ ] 상품을 클릭하면 상품 상세로 이동한다.
- [ ] 최근 본 상품이 있는 경우 상품 목록 상단에서 10개까지 확인할 수 있다.
- [x] 상품을 클릭하면 상품 상세로 이동한다.
- [X] 툴바 안의 카트 버튼을 누르면 장바구니로 이동한다.
- [x] 툴바 안의 카트 옆에 장바구니 상품 수가 노출된다.
- [x] 상품 목록의 +버튼을 누르면 장바구니에 상품이 추가된다.
- [x] +버튼을 누른 후 동시에 수량 선택 버튼이 노출된다.
- [x] 상품 목록의 상품 수가 변화하면 장바구니에 반영된다.
- [x] 장바구니 상품 수가 변화하면 상품 목록에도 반영된다.
### ProductDetailActivity
- [ ] 사용자는 상품을 장바구니에 추가할 수 있다.
- [x] 사용자는 상품을 장바구니에 추가할 수 있다.
- [x] 마지막으로 본 상품 1개를 상품 상세 페이지에서 확인할 수 있다.
- [x] 마지막으로 본 상품을 선택했을 때는 마지막으로 본 상품이 보이지 않는다.
- [x] 마지막으로 본 상품 페이지에서 뒤로 가기를 하면 상품 목록으로 이동한다.
### CartActivity
- [ ] 장바구니에서 원하는 상품을 삭제할 수 있다.
- [x] 장바구니에서 원하는 상품을 삭제할 수 있다.
- [X] 툴바 안의 백버튼을 누르면 뒤로 이동한다.

- [x] 체크박스로 주문할 상품 범위를 조정할 할 수 있다.
- [x] 전체 체크박스를 선택하면 해당 페이지 내의 상품들만 선택된다.
- [x] 페이지가 바뀌어도 선택 항목은 유지된다.
3 changes: 2 additions & 1 deletion app/build.gradle.kts
Original file line number Diff line number Diff line change
Expand Up @@ -46,10 +46,11 @@ dependencies {
implementation("androidx.appcompat:appcompat:1.6.0")
implementation("com.google.android.material:material:1.7.0")
implementation("androidx.constraintlayout:constraintlayout:2.1.4")
implementation(project(mapOf("path" to ":domain")))
implementation(project(":domain"))
testImplementation("junit:junit:4.13.2")
androidTestImplementation("androidx.test.ext:junit:1.1.5")
androidTestImplementation("androidx.test.espresso:espresso-core:3.5.1")
implementation("com.github.bumptech.glide:glide:4.15.1")
testImplementation("io.mockk:mockk:1.13.5")
testImplementation("androidx.arch.core:core-testing:2.2.0")
}
3 changes: 2 additions & 1 deletion app/src/main/AndroidManifest.xml
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@
<uses-permission android:name="android.permission.INTERNET" />

<application
android:usesCleartextTraffic="true"
android:allowBackup="true"
android:dataExtractionRules="@xml/data_extraction_rules"
android:fullBackupContent="@xml/backup_rules"
Expand All @@ -19,7 +20,7 @@
<activity
android:name=".view.cart.CartActivity"
android:exported="false"
android:label="Cart" />
android:label="@string/label_cart" />
<activity
android:name=".view.productlist.ProductListActivity"
android:exported="true">
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ import android.content.ContentValues
import android.content.Context
import android.database.sqlite.SQLiteDatabase
import android.database.sqlite.SQLiteOpenHelper
import woowacourse.shopping.domain.CartProduct
import woowacourse.shopping.domain.model.CartProduct

class CartDBHelper(context: Context) : SQLiteOpenHelper(context, "cart", null, 1) {
override fun onCreate(db: SQLiteDatabase?) {
Expand Down
Original file line number Diff line number Diff line change
@@ -1,17 +1,17 @@
package woowacourse.shopping.data
package woowacourse.shopping.data.repository

import android.content.Context
import woowacourse.shopping.data.db.CartDBHelper
import woowacourse.shopping.domain.CartProduct
import woowacourse.shopping.domain.CartRepository
import woowacourse.shopping.domain.model.CartProduct
import woowacourse.shopping.domain.repository.CartRepository

class CartDbRepository(context: Context) : CartRepository {
private val dbHelper = CartDBHelper(context)
override fun findAll(): List<CartProduct> {
return dbHelper.selectAll()
}

private fun find(id: Int): CartProduct? {
override fun find(id: Int): CartProduct? {
return dbHelper.selectWhereId(id)
}

Expand All @@ -24,6 +24,10 @@ class CartDbRepository(context: Context) : CartRepository {
dbHelper.insert(id, count)
}

override fun update(id: Int, count: Int) {
dbHelper.update(id, count)
}

override fun remove(id: Int) {
dbHelper.remove(id)
}
Expand Down
Original file line number Diff line number Diff line change
@@ -1,8 +1,8 @@
package woowacourse.shopping.data
package woowacourse.shopping.data.repository

import woowacourse.shopping.domain.Price
import woowacourse.shopping.domain.Product
import woowacourse.shopping.domain.ProductRepository
import woowacourse.shopping.domain.model.Price
import woowacourse.shopping.domain.model.Product
import woowacourse.shopping.domain.repository.ProductRepository

object ProductMockRepository : ProductRepository {
private val products = listOf(
Expand Down
Original file line number Diff line number Diff line change
@@ -1,8 +1,8 @@
package woowacourse.shopping.data
package woowacourse.shopping.data.repository

import android.content.Context
import woowacourse.shopping.data.db.RecentViewedDBHelper
import woowacourse.shopping.domain.RecentViewedRepository
import woowacourse.shopping.domain.repository.RecentViewedRepository

class RecentViewedDbRepository(context: Context) :
RecentViewedRepository {
Expand Down
Original file line number Diff line number Diff line change
@@ -1,7 +1,9 @@
package woowacourse.shopping.model

import woowacourse.shopping.domain.CartProduct
import woowacourse.shopping.domain.Product
import woowacourse.shopping.domain.model.CartProduct
import woowacourse.shopping.domain.model.Product

fun CartProduct.toUiModel(product: Product): CartProductModel =
CartProductModel(id, product.name, product.imageUrl, count, product.price.price * count)
fun CartProduct.toUiModel(isChecked: Boolean = false, product: Product): CartProductModel =
CartProductModel(isChecked, id, product.name, product.imageUrl, count, product.price.price)

fun CartProductModel.toDomain(): CartProduct = CartProduct(id, count)
Original file line number Diff line number Diff line change
@@ -1,3 +1,3 @@
package woowacourse.shopping.model

data class CartProductModel(val id: Int, val name: String, val imageUrl: String, val count: Int, val totalPrice: Int)
data class CartProductModel(val isChecked: Boolean, val id: Int, val name: String, val imageUrl: String, val count: Int, val price: Int)
4 changes: 2 additions & 2 deletions app/src/main/java/woowacourse/shopping/model/ProductMapper.kt
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
package woowacourse.shopping.model

import woowacourse.shopping.domain.Product
import woowacourse.shopping.domain.model.Product

fun Product.toUiModel() = ProductModel(id, name, imageUrl, price.price)
fun Product.toUiModel(count: Int = 0) = ProductModel(id, name, imageUrl, price.price, count)
Original file line number Diff line number Diff line change
Expand Up @@ -4,4 +4,4 @@ import android.os.Parcelable
import kotlinx.parcelize.Parcelize

@Parcelize
data class ProductModel(val id: Int, val name: String, val imageUrl: String, val price: Int) : Parcelable
data class ProductModel(val id: Int, val name: String, val imageUrl: String, val price: Int, val count: Int) : Parcelable
52 changes: 36 additions & 16 deletions app/src/main/java/woowacourse/shopping/view/cart/CartActivity.kt
Original file line number Diff line number Diff line change
Expand Up @@ -5,34 +5,46 @@ import android.content.Intent
import android.os.Bundle
import android.view.MenuItem
import androidx.appcompat.app.AppCompatActivity
import woowacourse.shopping.data.CartDbRepository
import woowacourse.shopping.data.ProductMockRepository
import woowacourse.shopping.data.repository.CartDbRepository
import woowacourse.shopping.databinding.ActivityCartBinding
import woowacourse.shopping.domain.data.MockServer
import woowacourse.shopping.domain.repository.ProductRemoteRepository
import woowacourse.shopping.model.CartProductModel
import woowacourse.shopping.view.productlist.ProductListActivity

class CartActivity : AppCompatActivity(), CartContract.View {
private lateinit var binding: ActivityCartBinding
private lateinit var presenter: CartContract.Presenter
private lateinit var mockWebServer: MockServer
private val binding: ActivityCartBinding by lazy { ActivityCartBinding.inflate(layoutInflater) }
private val presenter: CartContract.Presenter by lazy {
CartPresenter(
this,
CartDbRepository(this),
ProductRemoteRepository(mockWebServer.url),
)
}

override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setUpMockServer()
setUpBinding()
setContentView(binding.root)
setUpActionBar()
setUpPresenter()
presenter.fetchProducts()
}

private fun setUpMockServer() {
val thread = Thread { mockWebServer = MockServer() }
thread.start()
thread.join()
}
Comment on lines +35 to +39

Choose a reason for hiding this comment

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

MockWebServer를 초기화하는 코드를 잘 구현해주셨네요. 👍
다만, WebServer를 제어하는 역할이 View에서 하는 것이 적절할까요?
MVP 패턴에서의 View의 역할에 대해서 다시 한번 고민해보세요. (이하 관련 내용 동일)

Copy link
Author

Choose a reason for hiding this comment

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

MockWebServer를 구동시키는 것이 View의 역할이 아닌 것 같다고 생각하고 있었으나, Presenter에게 MockWebServer의 url을 넘겨주어야해서 어떻게 수정해야할지 잘 모르겠습니다.
실제 서버라면 Server를 켤 필요 없이 서버의 url을 넘겨주면 될텐데요...
이런 경우에는 어떻게 분리해야할까요..?

Choose a reason for hiding this comment

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

MockWebServer의 url이 아닌 MockWebServer를 넘기도록 구현해도 되지 않을까요?

Copy link
Author

Choose a reason for hiding this comment

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

지금은 MockWebServer을 사용하니까 그래도 되겠네요!


private fun setUpBinding() {
binding = ActivityCartBinding.inflate(layoutInflater)
binding.lifecycleOwner = this
binding.presenter = presenter
}

private fun setUpActionBar() {
supportActionBar?.setDisplayHomeAsUpEnabled(true)
supportActionBar?.title = TITLE
}

private fun setUpPresenter() {
presenter = CartPresenter(this, CartDbRepository(this), ProductMockRepository)
}

override fun showProducts(items: List<CartViewItem>) {
Expand All @@ -50,29 +62,37 @@ class CartActivity : AppCompatActivity(), CartContract.View {
override fun onPrevClick() {
presenter.fetchPrevPage()
}
}

override fun onUpdateCount(id: Int, count: Int) {
presenter.updateCartProductCount(id, count)
}

override fun onSelectProduct(product: CartProductModel) {
presenter.checkProduct(product)
}
},
)
}

override fun showOtherPage() {
override fun showChangedItems() {
binding.recyclerCart.adapter?.notifyDataSetChanged()
}

override fun notifyRemoveItem(position: Int) {
binding.recyclerCart.adapter?.notifyItemRemoved(position)
override fun showChangedItem(position: Int) {
binding.recyclerCart.adapter?.notifyItemChanged(position)
}

override fun onOptionsItemSelected(item: MenuItem): Boolean {
when (item.itemId) {
android.R.id.home -> {
setResult(ProductListActivity.RESULT_VISIT_CART, intent)
finish()
}
}
return super.onOptionsItemSelected(item)
}

companion object {
private const val TITLE = "Cart"
fun newIntent(context: Context): Intent = Intent(context, CartActivity::class.java)
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ package woowacourse.shopping.view.cart

import android.view.ViewGroup
import androidx.recyclerview.widget.RecyclerView
import woowacourse.shopping.model.CartProductModel

class CartAdapter(
private val items: List<CartViewItem>,
Expand All @@ -12,6 +13,8 @@ class CartAdapter(
fun onRemoveClick(id: Int)
fun onNextClick()
fun onPrevClick()
fun onUpdateCount(id: Int, count: Int)
fun onSelectProduct(product: CartProductModel)
}

override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): CartItemViewHolder {
Expand Down
18 changes: 15 additions & 3 deletions app/src/main/java/woowacourse/shopping/view/cart/CartContract.kt
Original file line number Diff line number Diff line change
@@ -1,16 +1,28 @@
package woowacourse.shopping.view.cart

import androidx.lifecycle.LiveData
import woowacourse.shopping.domain.cartsystem.CartPageStatus
import woowacourse.shopping.domain.cartsystem.CartSystemResult
import woowacourse.shopping.model.CartProductModel

interface CartContract {
interface View {
fun showProducts(items: List<CartViewItem>)
fun notifyRemoveItem(position: Int)
fun showOtherPage()
fun showChangedItems()
fun showChangedItem(position: Int)
}

interface Presenter {
val cartSystemResult: LiveData<CartSystemResult>
val cartPageStatus: LiveData<CartPageStatus>
val isCheckedAll: LiveData<Boolean>

fun fetchProducts()
fun removeProduct(id: Int)
fun fetchNextPage()
fun fetchPrevPage()
fun removeProduct(id: Int)
fun updateCartProductCount(id: Int, count: Int)
fun checkProduct(product: CartProductModel)
fun checkProductsAll()
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -5,10 +5,8 @@ import android.view.View
import android.view.ViewGroup
import androidx.recyclerview.widget.RecyclerView
import com.bumptech.glide.Glide
import woowacourse.shopping.R
import woowacourse.shopping.databinding.ItemCartBinding
import woowacourse.shopping.databinding.ItemCartPaginationBinding
import woowacourse.shopping.util.PriceFormatter

sealed class CartItemViewHolder(view: View) : RecyclerView.ViewHolder(view) {
class CartProductViewHolder(
Expand All @@ -22,10 +20,6 @@ sealed class CartItemViewHolder(view: View) : RecyclerView.ViewHolder(view) {

fun bind(item: CartViewItem.CartProductItem) {
binding.cartProduct = item.product
binding.textPrice.text = binding.root.context.getString(
R.string.korean_won,
PriceFormatter.format(item.product.totalPrice)
)
Glide.with(binding.root.context).load(item.product.imageUrl).into(binding.imgProduct)
}
}
Expand Down
Loading