diff --git a/README.md b/README.md
index fcfdf88ac..22267c7c8 100644
--- a/README.md
+++ b/README.md
@@ -23,3 +23,13 @@
- 최근 본 상품을 조회할 수 있다.
- 상품 목록을 보여준다.
- 최근 본 상품이 있는 경우 상품 목록 상단에서 10개까지 확인할 수 있다.
+- [ ] 상품 목록에서 장바구니에 담을 상품의 수를 선택할 수 있다. (B마트 UX 참고)
+- [ ] 버튼을 누르면 장바구니에 상품이 추가됨과 동시에 수량 선택 버튼이 노출된다.
+- [ ] 상품 목록의 상품 수가 변화하면 장바구니에도 반영되어야 한다.
+- [ ] 장바구니의 상품 수가 변화하면 상품 목록에도 반영되어야 한다.
+- [ ] 장바구니 화면에서 체크박스로 주문할 상품 범위를 조정할 수 있다.
+- [ ] 전체 체크박스를 선택하면 해당 페이지 내의 상품들만 선택된다.
+- [ ] 페이지가 바뀌어도 선택된 항목은 유지된다.
+- [ ] 마지막으로 본 상품 1개를 상품 상세 페이지에서 확인할 수 있다.
+- [ ] 마지막으로 본 상품을 선택했을 때는 마지막으로 본 상품이 보이지 않는다.
+- [ ] 마지막으로 본 상품 페이지에서 뒤로 가기를 하면 상품 목록으로 이동한다.
diff --git a/app/build.gradle.kts b/app/build.gradle.kts
index b3a6c846d..25fe8a882 100644
--- a/app/build.gradle.kts
+++ b/app/build.gradle.kts
@@ -60,4 +60,12 @@ dependencies {
// concatAdapter
implementation("androidx.recyclerview:recyclerview:1.3.0")
+
+ // core-testing
+ testImplementation("androidx.arch.core:core-testing:2.2.0")
+
+ // OkHttp
+ implementation("com.squareup.okhttp3:okhttp:4.11.0")
+ implementation("com.squareup.okhttp3:mockwebserver:4.11.0")
+ testImplementation("com.squareup.okhttp3:mockwebserver:4.11.0")
}
diff --git a/app/src/main/AndroidManifest.xml b/app/src/main/AndroidManifest.xml
index 20464efff..164335ce8 100644
--- a/app/src/main/AndroidManifest.xml
+++ b/app/src/main/AndroidManifest.xml
@@ -12,12 +12,13 @@
android:label="@string/app_name"
android:supportsRtl="true"
android:theme="@style/Theme.Shopping"
+ android:usesCleartextTraffic="true"
tools:targetApi="31">
- fun add(product: DataProduct)
- fun remove(product: DataProduct)
-}
diff --git a/app/src/main/java/woowacourse/shopping/data/database/dao/basket/BasketDaoImpl.kt b/app/src/main/java/woowacourse/shopping/data/database/dao/basket/BasketDaoImpl.kt
deleted file mode 100644
index 84469b529..000000000
--- a/app/src/main/java/woowacourse/shopping/data/database/dao/basket/BasketDaoImpl.kt
+++ /dev/null
@@ -1,60 +0,0 @@
-package woowacourse.shopping.data.database.dao.basket
-
-import android.annotation.SuppressLint
-import android.content.ContentValues
-import android.provider.BaseColumns
-import woowacourse.shopping.data.database.ShoppingDatabase
-import woowacourse.shopping.data.database.contract.BasketContract
-import woowacourse.shopping.data.model.DataPageNumber
-import woowacourse.shopping.data.model.DataPrice
-import woowacourse.shopping.data.model.DataProduct
-import woowacourse.shopping.util.extension.safeSubList
-
-class BasketDaoImpl(private val database: ShoppingDatabase) : BasketDao {
- @SuppressLint("Range")
- override fun getPartially(page: DataPageNumber): List {
- val products = mutableListOf()
-
- val db = database.writableDatabase
- val cursor = db.rawQuery(GET_ALL_QUERY, null)
-
- while (cursor.moveToNext()) {
- val id: Int = cursor.getInt(cursor.getColumnIndex(BaseColumns._ID))
- val name: String =
- cursor.getString(cursor.getColumnIndex(BasketContract.COLUMN_NAME))
- val price: DataPrice =
- DataPrice(cursor.getInt(cursor.getColumnIndex(BasketContract.COLUMN_PRICE)))
- val imageUrl: String =
- cursor.getString(cursor.getColumnIndex(BasketContract.COLUMN_IMAGE_URL))
- products.add(DataProduct(id, name, price, imageUrl))
- }
- cursor.close()
-
- return products.safeSubList(page.start, page.end)
- }
-
- override fun add(product: DataProduct) {
- val contentValues = ContentValues().apply {
- put(BasketContract.COLUMN_NAME, product.name)
- put(BasketContract.COLUMN_PRICE, product.price.value)
- put(BasketContract.COLUMN_IMAGE_URL, product.imageUrl)
- put(BasketContract.COLUMN_CREATED, System.currentTimeMillis())
- }
-
- database.writableDatabase.insert(BasketContract.TABLE_NAME, null, contentValues)
- }
-
- override fun remove(product: DataProduct) {
- database.writableDatabase.delete(
- BasketContract.TABLE_NAME,
- "${BaseColumns._ID} = ?",
- arrayOf(product.id.toString())
- )
- }
-
- companion object {
- private val GET_ALL_QUERY = """
- SELECT * FROM ${BasketContract.TABLE_NAME}
- """.trimIndent()
- }
-}
diff --git a/app/src/main/java/woowacourse/shopping/data/database/dao/cart/CartDao.kt b/app/src/main/java/woowacourse/shopping/data/database/dao/cart/CartDao.kt
new file mode 100644
index 000000000..5582eac7b
--- /dev/null
+++ b/app/src/main/java/woowacourse/shopping/data/database/dao/cart/CartDao.kt
@@ -0,0 +1,24 @@
+package woowacourse.shopping.data.database.dao.cart
+
+import woowacourse.shopping.data.entity.CartEntity
+import woowacourse.shopping.data.model.DataCart
+import woowacourse.shopping.data.model.DataCartProduct
+import woowacourse.shopping.data.model.DataPage
+import woowacourse.shopping.data.model.Product
+
+interface CartDao {
+ fun getCartEntitiesByPage(page: DataPage): List
+ fun insert(product: Product, count: Int)
+ fun deleteByProductId(id: Int)
+ fun contains(product: Product): Boolean
+ fun count(product: Product): Int
+ fun getProductInCartSize(): Int
+ fun addProductCount(product: Product, count: Int)
+ fun minusProductCount(product: Product, count: Int)
+ fun update(cartProduct: DataCartProduct)
+ fun updateCount(product: Product, count: Int)
+ fun getCheckedProductCount(): Int
+ fun deleteCheckedProducts()
+ fun getAllCartEntity(): List
+ fun getCartEntity(productId: Int): CartEntity
+}
diff --git a/app/src/main/java/woowacourse/shopping/data/database/dao/cart/CartDaoImpl.kt b/app/src/main/java/woowacourse/shopping/data/database/dao/cart/CartDaoImpl.kt
new file mode 100644
index 000000000..dc6baf24f
--- /dev/null
+++ b/app/src/main/java/woowacourse/shopping/data/database/dao/cart/CartDaoImpl.kt
@@ -0,0 +1,250 @@
+package woowacourse.shopping.data.database.dao.cart
+
+import android.annotation.SuppressLint
+import android.content.ContentValues
+import android.provider.BaseColumns
+import android.util.Log
+import woowacourse.shopping.data.database.ShoppingDatabase
+import woowacourse.shopping.data.database.contract.CartContract
+import woowacourse.shopping.data.database.contract.ProductContract
+import woowacourse.shopping.data.entity.CartEntity
+import woowacourse.shopping.data.model.CartProduct
+import woowacourse.shopping.data.model.DataCart
+import woowacourse.shopping.data.model.DataCartProduct
+import woowacourse.shopping.data.model.DataPage
+import woowacourse.shopping.data.model.DataPrice
+import woowacourse.shopping.data.model.Product
+import woowacourse.shopping.data.model.ProductCount
+import woowacourse.shopping.util.extension.safeSubList
+
+class CartDaoImpl(private val database: ShoppingDatabase) : CartDao {
+ @SuppressLint("Range")
+ override fun getAllCartEntity(): List {
+ val db = database.readableDatabase
+ val cartEntities = mutableListOf()
+ val cursor = db.rawQuery(GET_ALL_CART_ENTITY_QUERY, null)
+ while (cursor.moveToNext()) {
+ val cartId: Int =
+ cursor.getInt(cursor.getColumnIndex(CartContract.CART_ID))
+ val productId: Int =
+ cursor.getInt(cursor.getColumnIndex(CartContract.PRODUCT_ID))
+ val count: Int =
+ cursor.getInt(cursor.getColumnIndex(CartContract.COLUMN_COUNT))
+ val isChecked: Int =
+ cursor.getInt(cursor.getColumnIndex(CartContract.COLUMN_CHECKED))
+ cartEntities.add(CartEntity(cartId, productId, count, isChecked))
+ }
+ cursor.close()
+ return cartEntities
+ }
+
+ @SuppressLint("Range")
+ override fun getCartEntity(productId: Int): CartEntity {
+ val db = database.readableDatabase
+ val cursor = db.rawQuery(GET_CART_ENTITY_QUERY, arrayOf(productId.toString()))
+ val cartEntity = if (cursor.moveToNext()) {
+ val cartId: Int =
+ cursor.getInt(cursor.getColumnIndex(CartContract.CART_ID))
+ val count: Int =
+ cursor.getInt(cursor.getColumnIndex(CartContract.COLUMN_COUNT))
+ val isChecked: Int =
+ cursor.getInt(cursor.getColumnIndex(CartContract.COLUMN_CHECKED))
+ CartEntity(cartId, productId, count, isChecked)
+ } else {
+ CartEntity(0, productId, 0, 0)
+ }
+ cursor.close()
+ return cartEntity
+ }
+
+ @SuppressLint("Range")
+ override fun getCartEntitiesByPage(page: DataPage): List {
+ val cartEntities = mutableListOf()
+
+ val db = database.readableDatabase
+ val cursor = db.rawQuery(GET_ALL_CART_ENTITY_QUERY, null)
+
+ while (cursor.moveToNext()) {
+ val cartId: Int =
+ cursor.getInt(cursor.getColumnIndex(CartContract.CART_ID))
+ val productId: Int =
+ cursor.getInt(cursor.getColumnIndex(CartContract.PRODUCT_ID))
+ val count: Int =
+ cursor.getInt(cursor.getColumnIndex(CartContract.COLUMN_COUNT))
+ val isChecked: Int =
+ cursor.getInt(cursor.getColumnIndex(CartContract.COLUMN_CHECKED))
+ cartEntities.add(CartEntity(cartId, productId, count, isChecked))
+ }
+ cursor.close()
+ return cartEntities.safeSubList(page.start, page.end + 1)
+ }
+
+
+ override fun insert(product: Product, count: Int) {
+ val contentValues = ContentValues().apply {
+ put(CartContract.PRODUCT_ID, product.id)
+ put(CartContract.COLUMN_CREATED, System.currentTimeMillis())
+ put(CartContract.COLUMN_COUNT, count)
+ }
+
+ database.writableDatabase.insert(CartContract.TABLE_NAME, null, contentValues)
+ }
+
+ override fun getProductInCartSize(): Int {
+ val db = database.writableDatabase
+ val cursor = db.rawQuery(GET_PRODUCT_IN_CART_SIZE, null)
+ cursor.moveToNext()
+
+ val productInCartSize = cursor.getInt(0)
+ cursor.close()
+ return productInCartSize
+ }
+
+ override fun deleteByProductId(id: Int) {
+ database.writableDatabase.delete(
+ CartContract.TABLE_NAME,
+ "${CartContract.PRODUCT_ID} = ?",
+ arrayOf(id.toString())
+ )
+ }
+
+ override fun update(cartProduct: DataCartProduct) {
+ val contentValues = ContentValues().apply {
+ put(CartContract.PRODUCT_ID, cartProduct.product.id)
+ put(CartContract.COLUMN_COUNT, cartProduct.selectedCount.value)
+ put(CartContract.COLUMN_CHECKED, cartProduct.isChecked)
+ }
+
+ database.writableDatabase.update(
+ CartContract.TABLE_NAME,
+ contentValues,
+ "${CartContract.PRODUCT_ID} = ?",
+ arrayOf(cartProduct.product.id.toString())
+ )
+ }
+
+ @SuppressLint("Range")
+ override fun addProductCount(product: Product, count: Int) {
+ when (val originCount = count(product)) {
+ 0 -> insert(product, count)
+ else -> updateCount(product, originCount + count)
+ }
+ }
+
+ @SuppressLint("Range")
+ override fun minusProductCount(product: Product, count: Int) {
+ when (val originCount = count(product)) {
+ 0 -> return
+ else -> updateCount(product, originCount - count)
+ }
+ }
+
+ override fun updateCount(product: Product, count: Int) {
+ val contentValues = ContentValues().apply {
+ put(CartContract.PRODUCT_ID, product.id)
+ put(CartContract.COLUMN_COUNT, count)
+ }
+
+ database.writableDatabase.update(
+ CartContract.TABLE_NAME,
+ contentValues,
+ "${CartContract.PRODUCT_ID} = ?",
+ arrayOf(product.id.toString())
+ )
+ }
+
+ override fun getCheckedProductCount(): Int {
+ val db = database.writableDatabase
+ val cursor = db.rawQuery(GET_CHECKED_PRODUCT_COUNT, null)
+ cursor.moveToNext()
+
+ val checkedProductCount = cursor.getInt(0)
+ cursor.close()
+ return checkedProductCount
+ }
+
+ override fun deleteCheckedProducts() {
+ database.writableDatabase.delete(
+ CartContract.TABLE_NAME,
+ "${CartContract.COLUMN_CHECKED} = ?",
+ arrayOf("1")
+ )
+ }
+
+ override fun contains(product: Product): Boolean {
+ val db = database.writableDatabase
+ val cursor = db.rawQuery(
+ """
+ SELECT * FROM ${CartContract.TABLE_NAME}
+ WHERE ${CartContract.PRODUCT_ID} = ?
+ """.trimIndent(), arrayOf(product.id.toString())
+ )
+
+ val result = cursor.count > 0
+ cursor.close()
+ return result
+ }
+
+ @SuppressLint("Range")
+ override fun count(product: Product): Int {
+ val db = database.writableDatabase
+ val cursor = db.rawQuery(
+ """
+ SELECT * FROM ${CartContract.TABLE_NAME}
+ WHERE ${CartContract.PRODUCT_ID} = ?
+ """.trimIndent(), arrayOf(product.id.toString())
+ )
+
+ val count = if (cursor.count > 0) {
+ cursor.moveToNext()
+ val realCount = cursor.getInt(cursor.getColumnIndex(CartContract.COLUMN_COUNT))
+ if (realCount == -1) 0 else realCount
+ } else {
+ 0
+ }
+
+ cursor.close()
+ return count
+ }
+
+ companion object {
+ private val GET_ALL_CART_ENTITY_QUERY = """
+ SELECT * FROM ${CartContract.TABLE_NAME}
+ """.trimIndent()
+
+ private val GET_CART_ENTITY_QUERY = """
+ SELECT * FROM ${CartContract.TABLE_NAME}
+ WHERE ${CartContract.PRODUCT_ID} = ?
+ """.trimIndent()
+
+ private val GET_ALL_CART_PRODUCT_QUERY = """
+ SELECT * FROM ${ProductContract.TABLE_NAME} as product
+ LEFT JOIN ${CartContract.TABLE_NAME} as cart
+ ON cart.${CartContract.PRODUCT_ID} = product.${BaseColumns._ID}
+ """.trimIndent()
+
+ private val GET_ALL_CART_PRODUCT_IN_CART_QUERY = """
+ SELECT * FROM ${ProductContract.TABLE_NAME} as product
+ LEFT JOIN ${CartContract.TABLE_NAME} as cart
+ ON cart.${CartContract.PRODUCT_ID} = product.${BaseColumns._ID}
+ WHERE ${CartContract.COLUMN_COUNT} > 0
+ """.trimIndent()
+
+ private val GET_PRODUCT_IN_CART_SIZE = """
+ SELECT SUM(${CartContract.COLUMN_COUNT}) FROM ${CartContract.TABLE_NAME}
+ WHERE ${CartContract.COLUMN_COUNT} > 0
+ """.trimIndent()
+
+ private val GET_TOTAL_PRICE = """
+ SELECT SUM(${ProductContract.COLUMN_PRICE} * ${CartContract.COLUMN_COUNT}) FROM ${ProductContract.TABLE_NAME} as product
+ LEFT JOIN ${CartContract.TABLE_NAME} as cart
+ ON cart.${CartContract.PRODUCT_ID} = product.${BaseColumns._ID}
+ WHERE ${CartContract.COLUMN_COUNT} > 0 AND ${CartContract.COLUMN_CHECKED} = 1
+ """.trimIndent()
+
+ private val GET_CHECKED_PRODUCT_COUNT = """
+ SELECT COUNT(*) FROM ${CartContract.TABLE_NAME}
+ WHERE ${CartContract.COLUMN_COUNT} > 0 AND ${CartContract.COLUMN_CHECKED} = 1
+ """.trimIndent()
+ }
+}
diff --git a/app/src/main/java/woowacourse/shopping/data/database/dao/product/ProductDao.kt b/app/src/main/java/woowacourse/shopping/data/database/dao/product/ProductDao.kt
deleted file mode 100644
index b46c3cb2e..000000000
--- a/app/src/main/java/woowacourse/shopping/data/database/dao/product/ProductDao.kt
+++ /dev/null
@@ -1,7 +0,0 @@
-package woowacourse.shopping.data.database.dao.product
-
-import woowacourse.shopping.data.model.DataProduct
-
-interface ProductDao {
- fun getPartially(size: Int, lastId: Int): List
-}
diff --git a/app/src/main/java/woowacourse/shopping/data/database/dao/product/ProductDaoImpl.kt b/app/src/main/java/woowacourse/shopping/data/database/dao/product/ProductDaoImpl.kt
deleted file mode 100644
index 22a7c6904..000000000
--- a/app/src/main/java/woowacourse/shopping/data/database/dao/product/ProductDaoImpl.kt
+++ /dev/null
@@ -1,49 +0,0 @@
-package woowacourse.shopping.data.database.dao.product
-
-import android.annotation.SuppressLint
-import android.content.ContentValues
-import android.database.sqlite.SQLiteOpenHelper
-import android.provider.BaseColumns
-import woowacourse.shopping.data.database.contract.ProductContract
-import woowacourse.shopping.data.model.DataPrice
-import woowacourse.shopping.data.model.DataProduct
-
-class ProductDaoImpl(private val database: SQLiteOpenHelper) : ProductDao {
- @SuppressLint("Range")
- override fun getPartially(size: Int, lastId: Int): List {
- val products = mutableListOf()
- val db = database.writableDatabase
- val cursor =
- db.rawQuery(GET_PARTIALLY_QUERY, arrayOf(lastId.toString(), size.toString()))
- while (cursor.moveToNext()) {
- val id: Int = cursor.getInt(cursor.getColumnIndex(BaseColumns._ID))
- val name: String =
- cursor.getString(cursor.getColumnIndex(ProductContract.COLUMN_NAME))
- val price: DataPrice =
- DataPrice(cursor.getInt(cursor.getColumnIndex(ProductContract.COLUMN_PRICE)))
- val imageUrl: String =
- cursor.getString(cursor.getColumnIndex(ProductContract.COLUMN_IMAGE_URL))
- products.add(DataProduct(id, name, price, imageUrl))
- }
- cursor.close()
- return products
- }
-
- fun add(product: DataProduct) {
- val contentValues = ContentValues().apply {
- put(ProductContract.COLUMN_NAME, product.name)
- put(ProductContract.COLUMN_PRICE, product.price.value)
- put(ProductContract.COLUMN_IMAGE_URL, product.imageUrl)
- }
-
- database.writableDatabase.insert(ProductContract.TABLE_NAME, null, contentValues)
- }
-
- companion object {
- private val GET_PARTIALLY_QUERY = """
- SELECT * FROM ${ProductContract.TABLE_NAME}
- WHERE ${BaseColumns._ID} > ?
- ORDER BY ${BaseColumns._ID} LIMIT ?
- """.trimIndent()
- }
-}
diff --git a/app/src/main/java/woowacourse/shopping/data/database/dao/recentproduct/RecentProductDao.kt b/app/src/main/java/woowacourse/shopping/data/database/dao/recentproduct/RecentProductDao.kt
index f67797bda..b63be5b22 100644
--- a/app/src/main/java/woowacourse/shopping/data/database/dao/recentproduct/RecentProductDao.kt
+++ b/app/src/main/java/woowacourse/shopping/data/database/dao/recentproduct/RecentProductDao.kt
@@ -4,7 +4,7 @@ import woowacourse.shopping.data.model.DataRecentProduct
interface RecentProductDao {
fun getSize(): Int
- fun getPartially(size: Int): List
- fun add(recentProduct: DataRecentProduct)
+ fun getRecentProductsPartially(size: Int): List
+ fun addRecentProduct(item: DataRecentProduct)
fun removeLast()
}
diff --git a/app/src/main/java/woowacourse/shopping/data/database/dao/recentproduct/RecentProductDaoImpl.kt b/app/src/main/java/woowacourse/shopping/data/database/dao/recentproduct/RecentProductDaoImpl.kt
index a3fa3866c..83cea561b 100644
--- a/app/src/main/java/woowacourse/shopping/data/database/dao/recentproduct/RecentProductDaoImpl.kt
+++ b/app/src/main/java/woowacourse/shopping/data/database/dao/recentproduct/RecentProductDaoImpl.kt
@@ -7,8 +7,8 @@ import android.provider.BaseColumns
import woowacourse.shopping.data.database.contract.ProductContract
import woowacourse.shopping.data.database.contract.RecentProductContract
import woowacourse.shopping.data.model.DataPrice
-import woowacourse.shopping.data.model.DataProduct
import woowacourse.shopping.data.model.DataRecentProduct
+import woowacourse.shopping.data.model.Product
class RecentProductDaoImpl(private val database: SQLiteOpenHelper) : RecentProductDao {
@@ -24,7 +24,7 @@ class RecentProductDaoImpl(private val database: SQLiteOpenHelper) : RecentProdu
}
@SuppressLint("Range")
- override fun getPartially(size: Int): List {
+ override fun getRecentProductsPartially(size: Int): List {
val products = mutableListOf()
val db = database.writableDatabase
val cursor = db.rawQuery(GET_PARTIALLY_QUERY, arrayOf(size.toString()))
@@ -38,17 +38,17 @@ class RecentProductDaoImpl(private val database: SQLiteOpenHelper) : RecentProdu
DataPrice(cursor.getInt(cursor.getColumnIndex(RecentProductContract.COLUMN_PRICE)))
val imageUrl: String =
cursor.getString(cursor.getColumnIndex(RecentProductContract.COLUMN_IMAGE_URL))
- products.add(DataRecentProduct(id, DataProduct(productId, name, price, imageUrl)))
+ products.add(DataRecentProduct(id, Product(productId, name, price, imageUrl)))
}
cursor.close()
return products
}
- override fun add(recentProduct: DataRecentProduct) {
+ override fun addRecentProduct(item: DataRecentProduct) {
val contentValues = ContentValues().apply {
- put(RecentProductContract.COLUMN_NAME, recentProduct.product.name)
- put(RecentProductContract.COLUMN_PRICE, recentProduct.product.price.value)
- put(RecentProductContract.COLUMN_IMAGE_URL, recentProduct.product.imageUrl)
+ put(RecentProductContract.COLUMN_NAME, item.product.name)
+ put(RecentProductContract.COLUMN_PRICE, item.product.price.value)
+ put(RecentProductContract.COLUMN_IMAGE_URL, item.product.imageUrl)
}
database.writableDatabase.insert(RecentProductContract.TABLE_NAME, null, contentValues)
diff --git a/app/src/main/java/woowacourse/shopping/data/datasource/basket/BasketDataSource.kt b/app/src/main/java/woowacourse/shopping/data/datasource/basket/BasketDataSource.kt
deleted file mode 100644
index f5f185efd..000000000
--- a/app/src/main/java/woowacourse/shopping/data/datasource/basket/BasketDataSource.kt
+++ /dev/null
@@ -1,14 +0,0 @@
-package woowacourse.shopping.data.datasource.basket
-
-import woowacourse.shopping.data.model.DataPageNumber
-import woowacourse.shopping.data.model.DataProduct
-
-interface BasketDataSource {
- interface Local {
- fun getPartially(page: DataPageNumber): List
- fun add(product: DataProduct)
- fun remove(product: DataProduct)
- }
-
- interface Remote
-}
diff --git a/app/src/main/java/woowacourse/shopping/data/datasource/basket/LocalBasketDataSource.kt b/app/src/main/java/woowacourse/shopping/data/datasource/basket/LocalBasketDataSource.kt
deleted file mode 100644
index 4dae3bdc7..000000000
--- a/app/src/main/java/woowacourse/shopping/data/datasource/basket/LocalBasketDataSource.kt
+++ /dev/null
@@ -1,18 +0,0 @@
-package woowacourse.shopping.data.datasource.basket
-
-import woowacourse.shopping.data.database.dao.basket.BasketDao
-import woowacourse.shopping.data.model.DataPageNumber
-import woowacourse.shopping.data.model.DataProduct
-
-class LocalBasketDataSource(private val dao: BasketDao) : BasketDataSource.Local {
- override fun getPartially(page: DataPageNumber): List =
- dao.getPartially(page)
-
- override fun add(product: DataProduct) {
- dao.add(product)
- }
-
- override fun remove(product: DataProduct) {
- dao.remove(product)
- }
-}
diff --git a/app/src/main/java/woowacourse/shopping/data/datasource/cart/CartDataSource.kt b/app/src/main/java/woowacourse/shopping/data/datasource/cart/CartDataSource.kt
new file mode 100644
index 000000000..d1cb56023
--- /dev/null
+++ b/app/src/main/java/woowacourse/shopping/data/datasource/cart/CartDataSource.kt
@@ -0,0 +1,23 @@
+package woowacourse.shopping.data.datasource.cart
+
+import woowacourse.shopping.data.entity.CartEntity
+import woowacourse.shopping.data.model.DataCart
+import woowacourse.shopping.data.model.DataCartProduct
+import woowacourse.shopping.data.model.DataPage
+import woowacourse.shopping.data.model.Product
+
+interface CartDataSource {
+ interface Local {
+ fun getAllCartEntity(): List
+ fun getCartEntity(productId: Int): CartEntity
+ fun increaseCartCount(product: Product, count: Int)
+ fun decreaseCartCount(product: Product, count: Int)
+ fun deleteByProductId(productId: Int)
+ fun getProductInCartSize(): Int
+ fun update(cartProducts: List)
+ fun getCheckedProductCount(): Int
+ fun removeCheckedProducts()
+ }
+
+ interface Remote
+}
diff --git a/app/src/main/java/woowacourse/shopping/data/datasource/cart/LocalCartDataSource.kt b/app/src/main/java/woowacourse/shopping/data/datasource/cart/LocalCartDataSource.kt
new file mode 100644
index 000000000..79b1dcfaf
--- /dev/null
+++ b/app/src/main/java/woowacourse/shopping/data/datasource/cart/LocalCartDataSource.kt
@@ -0,0 +1,43 @@
+package woowacourse.shopping.data.datasource.cart
+
+import woowacourse.shopping.data.database.dao.cart.CartDao
+import woowacourse.shopping.data.entity.CartEntity
+import woowacourse.shopping.data.model.DataCart
+import woowacourse.shopping.data.model.DataCartProduct
+import woowacourse.shopping.data.model.DataPage
+import woowacourse.shopping.data.model.Product
+
+class LocalCartDataSource(private val dao: CartDao) : CartDataSource.Local {
+ override fun getAllCartEntity(): List = dao.getAllCartEntity()
+
+ override fun getCartEntity(productId: Int): CartEntity = dao.getCartEntity(productId)
+
+ override fun increaseCartCount(product: Product, count: Int) {
+ dao.addProductCount(product, count)
+ }
+
+ override fun getProductInCartSize(): Int = dao.getProductInCartSize()
+
+ override fun update(cartProducts: List) {
+ cartProducts.forEach(dao::update)
+ }
+
+ override fun getCheckedProductCount(): Int = dao.getCheckedProductCount()
+
+ override fun removeCheckedProducts() {
+ dao.deleteCheckedProducts()
+ }
+
+ override fun decreaseCartCount(product: Product, count: Int) {
+ val productCount = dao.count(product)
+ when {
+ !dao.contains(product) -> return
+ productCount > count -> dao.minusProductCount(product, count)
+ else -> deleteByProductId(product.id)
+ }
+ }
+
+ override fun deleteByProductId(productId: Int) {
+ dao.deleteByProductId(productId)
+ }
+}
diff --git a/app/src/main/java/woowacourse/shopping/data/datasource/product/LocalProductDataSource.kt b/app/src/main/java/woowacourse/shopping/data/datasource/product/LocalProductDataSource.kt
deleted file mode 100644
index 909e8c652..000000000
--- a/app/src/main/java/woowacourse/shopping/data/datasource/product/LocalProductDataSource.kt
+++ /dev/null
@@ -1,9 +0,0 @@
-package woowacourse.shopping.data.datasource.product
-
-import woowacourse.shopping.data.database.dao.product.ProductDao
-import woowacourse.shopping.data.model.DataProduct
-
-class LocalProductDataSource(private val dao: ProductDao) : ProductDataSource.Local {
- override fun getPartially(size: Int, lastId: Int): List =
- dao.getPartially(size, lastId)
-}
diff --git a/app/src/main/java/woowacourse/shopping/data/datasource/product/ProductDataSource.kt b/app/src/main/java/woowacourse/shopping/data/datasource/product/ProductDataSource.kt
index a1d650252..c61329319 100644
--- a/app/src/main/java/woowacourse/shopping/data/datasource/product/ProductDataSource.kt
+++ b/app/src/main/java/woowacourse/shopping/data/datasource/product/ProductDataSource.kt
@@ -1,11 +1,13 @@
package woowacourse.shopping.data.datasource.product
-import woowacourse.shopping.data.model.DataProduct
+import woowacourse.shopping.data.model.Page
+import woowacourse.shopping.data.model.Product
interface ProductDataSource {
- interface Local {
- fun getPartially(size: Int, lastId: Int): List
- }
+ interface Local
- interface Remote
+ interface Remote {
+ fun getProductByPage(page: Page): List
+ fun findProductById(id: Int): Product?
+ }
}
diff --git a/app/src/main/java/woowacourse/shopping/data/datasource/product/RemoteProductDataSource.kt b/app/src/main/java/woowacourse/shopping/data/datasource/product/RemoteProductDataSource.kt
new file mode 100644
index 000000000..aedfd8f25
--- /dev/null
+++ b/app/src/main/java/woowacourse/shopping/data/datasource/product/RemoteProductDataSource.kt
@@ -0,0 +1,89 @@
+package woowacourse.shopping.data.datasource.product
+
+import okhttp3.Call
+import okhttp3.Callback
+import okhttp3.OkHttpClient
+import okhttp3.Request
+import okhttp3.Response
+import org.json.JSONArray
+import org.json.JSONObject
+import woowacourse.shopping.data.model.Page
+import woowacourse.shopping.data.model.Price
+import woowacourse.shopping.data.model.Product
+import woowacourse.shopping.server.GET
+import woowacourse.shopping.server.ShoppingMockWebServer
+import java.io.IOException
+import java.util.concurrent.CountDownLatch
+
+class RemoteProductDataSource : ProductDataSource.Remote {
+ private val shoppingService: ShoppingMockWebServer = ShoppingMockWebServer()
+ private var BASE_URL: String
+
+ init {
+ shoppingService.start()
+ shoppingService.join()
+ BASE_URL = shoppingService.baseUrl
+ }
+
+ override fun getProductByPage(page: Page): List {
+ shoppingService.join()
+ val url = "${BASE_URL}/products?start=${page.start}&count=${page.sizePerPage}"
+ val httpClient = OkHttpClient()
+ val request = Request.Builder().url(url).method(GET, null).build()
+ val products = mutableListOf()
+ val countDownLatch = CountDownLatch(1)
+
+ httpClient.newCall(request).enqueue(object : Callback {
+ override fun onFailure(call: Call, e: IOException) {
+ countDownLatch.countDown()
+ }
+
+ override fun onResponse(call: Call, response: Response) {
+ val input = response.body?.string()
+ val jsonArray = JSONArray(input)
+ for (i in 0 until jsonArray.length()) {
+ val jsonObject = jsonArray.getJSONObject(i)
+ products.add(convertToProduct(jsonObject))
+ }
+ countDownLatch.countDown()
+ }
+ })
+
+ countDownLatch.await()
+ return products
+ }
+
+ override fun findProductById(id: Int): Product? {
+ shoppingService.join()
+ val url = "${BASE_URL}/products?productId=${id}"
+ val httpClient = OkHttpClient()
+ val request = Request.Builder().url(url).method(GET, null).build()
+ val countDownLatch = CountDownLatch(1)
+ var product: Product? = null
+
+ httpClient.newCall(request).enqueue(object : Callback {
+ override fun onFailure(call: Call, e: IOException) {
+ countDownLatch.countDown()
+ }
+
+ override fun onResponse(call: Call, response: Response) {
+ val input = response.body?.string()
+ val jsonObject = JSONObject(input)
+ if (jsonObject.getInt("id") == id) {
+ product = convertToProduct(jsonObject)
+ }
+ countDownLatch.countDown()
+ }
+ })
+
+ countDownLatch.await()
+ return product
+ }
+
+ private fun convertToProduct(response: JSONObject): Product = Product(
+ id = response.getInt("id"),
+ imageUrl = response.getString("imageUrl"),
+ name = response.getString("name"),
+ price = Price(response.getInt("price"))
+ )
+}
diff --git a/app/src/main/java/woowacourse/shopping/data/datasource/recentproduct/LocalRecentProductDataSource.kt b/app/src/main/java/woowacourse/shopping/data/datasource/recentproduct/LocalRecentProductDataSource.kt
index 5276e2904..ed5d640cf 100644
--- a/app/src/main/java/woowacourse/shopping/data/datasource/recentproduct/LocalRecentProductDataSource.kt
+++ b/app/src/main/java/woowacourse/shopping/data/datasource/recentproduct/LocalRecentProductDataSource.kt
@@ -6,13 +6,14 @@ import woowacourse.shopping.data.model.DataRecentProduct
class LocalRecentProductDataSource(private val dao: RecentProductDao) :
RecentProductDataSource.Local {
- override fun getPartially(size: Int): List = dao.getPartially(size)
+ override fun getPartially(size: Int): List =
+ dao.getRecentProductsPartially(size)
override fun add(product: DataRecentProduct) {
while (dao.getSize() >= STORED_DATA_SIZE) {
dao.removeLast()
}
- dao.add(product)
+ dao.addRecentProduct(product)
}
companion object {
diff --git a/app/src/main/java/woowacourse/shopping/data/entity/CartEntity.kt b/app/src/main/java/woowacourse/shopping/data/entity/CartEntity.kt
new file mode 100644
index 000000000..44f25b5ea
--- /dev/null
+++ b/app/src/main/java/woowacourse/shopping/data/entity/CartEntity.kt
@@ -0,0 +1,10 @@
+package woowacourse.shopping.data.entity
+
+typealias DataCartEntity = CartEntity
+
+class CartEntity(
+ val id: Int,
+ val productId: Int,
+ val count: Int,
+ val checked: Int,
+)
diff --git a/app/src/main/java/woowacourse/shopping/data/mapper/CartEntityMapper.kt b/app/src/main/java/woowacourse/shopping/data/mapper/CartEntityMapper.kt
new file mode 100644
index 000000000..a89e87c5e
--- /dev/null
+++ b/app/src/main/java/woowacourse/shopping/data/mapper/CartEntityMapper.kt
@@ -0,0 +1,12 @@
+package woowacourse.shopping.data.mapper
+
+import woowacourse.shopping.data.entity.DataCartEntity
+import woowacourse.shopping.domain.model.DomainCartEntity
+
+fun DataCartEntity.toDomain(): DomainCartEntity = DomainCartEntity(
+ id, productId, count, checked == 1
+)
+
+fun DomainCartEntity.toData(): DataCartEntity = DataCartEntity(
+ id, productId, count, if (checked) 1 else 0
+)
diff --git a/app/src/main/java/woowacourse/shopping/data/mapper/CartMapper.kt b/app/src/main/java/woowacourse/shopping/data/mapper/CartMapper.kt
new file mode 100644
index 000000000..1eb147fe3
--- /dev/null
+++ b/app/src/main/java/woowacourse/shopping/data/mapper/CartMapper.kt
@@ -0,0 +1,12 @@
+package woowacourse.shopping.data.mapper
+
+import woowacourse.shopping.data.model.DataCart
+import woowacourse.shopping.domain.model.DomainCart
+
+fun DataCart.toDomain(): DomainCart = DomainCart(
+ items = cartProducts.map { it.toDomain() },
+)
+
+fun DomainCart.toData(): DataCart = DataCart(
+ cartProducts = items.map { it.toData() },
+)
diff --git a/app/src/main/java/woowacourse/shopping/data/mapper/CartProductMapper.kt b/app/src/main/java/woowacourse/shopping/data/mapper/CartProductMapper.kt
new file mode 100644
index 000000000..90e6b3de1
--- /dev/null
+++ b/app/src/main/java/woowacourse/shopping/data/mapper/CartProductMapper.kt
@@ -0,0 +1,21 @@
+package woowacourse.shopping.data.mapper
+
+import woowacourse.shopping.data.model.DataCartProduct
+import woowacourse.shopping.domain.model.CartProduct
+import woowacourse.shopping.domain.model.DomainCartProduct
+
+fun DataCartProduct.toDomain(): CartProduct = CartProduct(
+ id = id,
+ product = product.toDomain(),
+ selectedCount = selectedCount.toDomain(),
+ isChecked = isChecked == 1,
+)
+
+fun CartProduct.toData(): DataCartProduct = DataCartProduct(
+ id = id,
+ product = product.toData(),
+ selectedCount = selectedCount.toData(),
+ isChecked = if (isChecked) 1 else 0,
+)
+
+fun List.toData(): List = map { it.toData() }
diff --git a/app/src/main/java/woowacourse/shopping/data/mapper/PageMapper.kt b/app/src/main/java/woowacourse/shopping/data/mapper/PageMapper.kt
new file mode 100644
index 000000000..3293a870a
--- /dev/null
+++ b/app/src/main/java/woowacourse/shopping/data/mapper/PageMapper.kt
@@ -0,0 +1,7 @@
+package woowacourse.shopping.data.mapper
+
+import woowacourse.shopping.data.model.DataPage
+import woowacourse.shopping.domain.model.page.DomainPage
+
+fun DomainPage.toData(extraSize: Int = 0): DataPage =
+ DataPage(value = value, sizePerPage = sizePerPage + extraSize)
diff --git a/app/src/main/java/woowacourse/shopping/data/mapper/PageNumberMapper.kt b/app/src/main/java/woowacourse/shopping/data/mapper/PageNumberMapper.kt
deleted file mode 100644
index 14e270dde..000000000
--- a/app/src/main/java/woowacourse/shopping/data/mapper/PageNumberMapper.kt
+++ /dev/null
@@ -1,9 +0,0 @@
-package woowacourse.shopping.data.mapper
-
-import woowacourse.shopping.data.model.DataPageNumber
-import woowacourse.shopping.domain.DomainPageNumber
-
-fun DataPageNumber.toDomain(): DomainPageNumber = DomainPageNumber(value = value)
-
-fun DomainPageNumber.toData(): DataPageNumber =
- DataPageNumber(value = value, sizePerPage = sizePerPage)
diff --git a/app/src/main/java/woowacourse/shopping/data/mapper/PriceMapper.kt b/app/src/main/java/woowacourse/shopping/data/mapper/PriceMapper.kt
index 9f09d5c2e..e039adca1 100644
--- a/app/src/main/java/woowacourse/shopping/data/mapper/PriceMapper.kt
+++ b/app/src/main/java/woowacourse/shopping/data/mapper/PriceMapper.kt
@@ -1,7 +1,7 @@
package woowacourse.shopping.data.mapper
import woowacourse.shopping.data.model.DataPrice
-import woowacourse.shopping.domain.Price
+import woowacourse.shopping.domain.model.Price
fun DataPrice.toDomain(): Price =
Price(value)
diff --git a/app/src/main/java/woowacourse/shopping/data/mapper/ProductCountMapper.kt b/app/src/main/java/woowacourse/shopping/data/mapper/ProductCountMapper.kt
new file mode 100644
index 000000000..3271a2554
--- /dev/null
+++ b/app/src/main/java/woowacourse/shopping/data/mapper/ProductCountMapper.kt
@@ -0,0 +1,10 @@
+package woowacourse.shopping.data.mapper
+
+import woowacourse.shopping.data.model.DataProductCount
+import woowacourse.shopping.domain.model.ProductCount
+
+fun DataProductCount.toDomain(): ProductCount =
+ ProductCount(value)
+
+fun ProductCount.toData(): DataProductCount =
+ DataProductCount(value)
diff --git a/app/src/main/java/woowacourse/shopping/data/mapper/ProductMapper.kt b/app/src/main/java/woowacourse/shopping/data/mapper/ProductMapper.kt
index 699cc27cf..4d1b2dd7f 100644
--- a/app/src/main/java/woowacourse/shopping/data/mapper/ProductMapper.kt
+++ b/app/src/main/java/woowacourse/shopping/data/mapper/ProductMapper.kt
@@ -1,10 +1,21 @@
package woowacourse.shopping.data.mapper
import woowacourse.shopping.data.model.DataProduct
-import woowacourse.shopping.domain.Product
+import woowacourse.shopping.domain.model.Product
fun DataProduct.toDomain(): Product =
- Product(id = id, name = name, price = price.toDomain(), imageUrl = imageUrl)
+ Product(
+ id = id,
+ name = name,
+ price = price.toDomain(),
+ imageUrl = imageUrl,
+ )
fun Product.toData(): DataProduct =
- DataProduct(id = id, name = name, price = price.toData(), imageUrl = imageUrl)
+ DataProduct(
+ id = id,
+ name = name,
+ price = price.toData(),
+ imageUrl = imageUrl,
+ )
+
diff --git a/app/src/main/java/woowacourse/shopping/data/mapper/RecentProductMapper.kt b/app/src/main/java/woowacourse/shopping/data/mapper/RecentProductMapper.kt
index ed50923c2..79bd048e2 100644
--- a/app/src/main/java/woowacourse/shopping/data/mapper/RecentProductMapper.kt
+++ b/app/src/main/java/woowacourse/shopping/data/mapper/RecentProductMapper.kt
@@ -1,10 +1,12 @@
package woowacourse.shopping.data.mapper
import woowacourse.shopping.data.model.DataRecentProduct
-import woowacourse.shopping.domain.RecentProduct
+import woowacourse.shopping.domain.model.RecentProduct
fun DataRecentProduct.toDomain(): RecentProduct =
RecentProduct(id = id, product = product.toDomain())
fun RecentProduct.toData(): DataRecentProduct =
DataRecentProduct(id = id, product = product.toData())
+
+fun List.toDomain(): List = map { it.toDomain() }
diff --git a/app/src/main/java/woowacourse/shopping/data/model/Cart.kt b/app/src/main/java/woowacourse/shopping/data/model/Cart.kt
new file mode 100644
index 000000000..7d0a97cff
--- /dev/null
+++ b/app/src/main/java/woowacourse/shopping/data/model/Cart.kt
@@ -0,0 +1,7 @@
+package woowacourse.shopping.data.model
+
+typealias DataCart = Cart
+
+data class Cart(
+ val cartProducts: List = emptyList(),
+)
diff --git a/app/src/main/java/woowacourse/shopping/data/model/CartProduct.kt b/app/src/main/java/woowacourse/shopping/data/model/CartProduct.kt
new file mode 100644
index 000000000..efd82625c
--- /dev/null
+++ b/app/src/main/java/woowacourse/shopping/data/model/CartProduct.kt
@@ -0,0 +1,10 @@
+package woowacourse.shopping.data.model
+
+typealias DataCartProduct = CartProduct
+
+data class CartProduct(
+ val id: Int,
+ val product: DataProduct,
+ val selectedCount: DataProductCount = DataProductCount(0),
+ val isChecked: Int,
+)
diff --git a/app/src/main/java/woowacourse/shopping/data/model/PageNumber.kt b/app/src/main/java/woowacourse/shopping/data/model/Page.kt
similarity index 56%
rename from app/src/main/java/woowacourse/shopping/data/model/PageNumber.kt
rename to app/src/main/java/woowacourse/shopping/data/model/Page.kt
index 52fdff57d..a1e44012d 100644
--- a/app/src/main/java/woowacourse/shopping/data/model/PageNumber.kt
+++ b/app/src/main/java/woowacourse/shopping/data/model/Page.kt
@@ -1,8 +1,8 @@
package woowacourse.shopping.data.model
-typealias DataPageNumber = PageNumber
+typealias DataPage = Page
-data class PageNumber(val value: Int, val sizePerPage: Int) {
+class Page(val value: Int, val sizePerPage: Int) {
val start = value * sizePerPage - sizePerPage
val end = value * sizePerPage + 1
}
diff --git a/app/src/main/java/woowacourse/shopping/data/model/ProductCount.kt b/app/src/main/java/woowacourse/shopping/data/model/ProductCount.kt
new file mode 100644
index 000000000..82e627b3a
--- /dev/null
+++ b/app/src/main/java/woowacourse/shopping/data/model/ProductCount.kt
@@ -0,0 +1,5 @@
+package woowacourse.shopping.data.model
+
+typealias DataProductCount = ProductCount
+
+class ProductCount(val value: Int)
diff --git a/app/src/main/java/woowacourse/shopping/data/model/RecentProduct.kt b/app/src/main/java/woowacourse/shopping/data/model/RecentProduct.kt
index 1c96e00b8..1468c7688 100644
--- a/app/src/main/java/woowacourse/shopping/data/model/RecentProduct.kt
+++ b/app/src/main/java/woowacourse/shopping/data/model/RecentProduct.kt
@@ -4,5 +4,5 @@ typealias DataRecentProduct = RecentProduct
data class RecentProduct(
val id: Int,
- val product: DataProduct,
+ val product: Product,
)
diff --git a/app/src/main/java/woowacourse/shopping/data/repository/BasketRepository.kt b/app/src/main/java/woowacourse/shopping/data/repository/BasketRepository.kt
deleted file mode 100644
index 70797aca1..000000000
--- a/app/src/main/java/woowacourse/shopping/data/repository/BasketRepository.kt
+++ /dev/null
@@ -1,22 +0,0 @@
-package woowacourse.shopping.data.repository
-
-import woowacourse.shopping.data.datasource.basket.BasketDataSource
-import woowacourse.shopping.data.mapper.toData
-import woowacourse.shopping.data.mapper.toDomain
-import woowacourse.shopping.domain.PageNumber
-import woowacourse.shopping.domain.Product
-import woowacourse.shopping.domain.repository.DomainBasketRepository
-
-class BasketRepository(private val localBasketDataSource: BasketDataSource.Local) :
- DomainBasketRepository {
- override fun getPartially(page: PageNumber): List =
- localBasketDataSource.getPartially(page.toData()).map { it.toDomain() }
-
- override fun add(product: Product) {
- localBasketDataSource.add(product.toData())
- }
-
- override fun remove(product: Product) {
- localBasketDataSource.remove(product.toData())
- }
-}
diff --git a/app/src/main/java/woowacourse/shopping/data/repository/CartRepositoryImpl.kt b/app/src/main/java/woowacourse/shopping/data/repository/CartRepositoryImpl.kt
new file mode 100644
index 000000000..11c9edec4
--- /dev/null
+++ b/app/src/main/java/woowacourse/shopping/data/repository/CartRepositoryImpl.kt
@@ -0,0 +1,46 @@
+package woowacourse.shopping.data.repository
+
+import woowacourse.shopping.data.datasource.cart.CartDataSource
+import woowacourse.shopping.data.mapper.toData
+import woowacourse.shopping.data.mapper.toDomain
+import woowacourse.shopping.domain.model.Cart
+import woowacourse.shopping.domain.model.CartEntity
+import woowacourse.shopping.domain.model.CartProduct
+import woowacourse.shopping.domain.model.Product
+import woowacourse.shopping.domain.model.page.Page
+import woowacourse.shopping.domain.repository.CartRepository
+
+class CartRepositoryImpl(private val localCartDataSource: CartDataSource.Local) :
+ CartRepository {
+ override fun getAllCartEntities(): List =
+ localCartDataSource.getAllCartEntity().map { it.toDomain() }
+
+ override fun getCartEntity(productId: Int): CartEntity =
+ localCartDataSource.getCartEntity(productId).toDomain()
+
+ override fun increaseCartCount(product: Product, count: Int) {
+ localCartDataSource.increaseCartCount(product.toData(), count)
+ }
+
+ override fun update(cartProducts: List) {
+ localCartDataSource.update(cartProducts.toData())
+ }
+
+ override fun getCheckedProductCount(): Int =
+ localCartDataSource.getCheckedProductCount()
+
+ override fun removeCheckedProducts() {
+ localCartDataSource.removeCheckedProducts()
+ }
+
+ override fun decreaseCartCount(product: Product, count: Int) {
+ localCartDataSource.decreaseCartCount(product.toData(), count)
+ }
+
+ override fun deleteByProductId(productId: Int) {
+ localCartDataSource.deleteByProductId(productId)
+ }
+
+ override fun getProductInCartSize(): Int =
+ localCartDataSource.getProductInCartSize()
+}
diff --git a/app/src/main/java/woowacourse/shopping/data/repository/ProductRepository.kt b/app/src/main/java/woowacourse/shopping/data/repository/ProductRepository.kt
deleted file mode 100644
index f3962a3f4..000000000
--- a/app/src/main/java/woowacourse/shopping/data/repository/ProductRepository.kt
+++ /dev/null
@@ -1,13 +0,0 @@
-package woowacourse.shopping.data.repository
-
-import woowacourse.shopping.data.datasource.product.ProductDataSource
-import woowacourse.shopping.data.mapper.toDomain
-import woowacourse.shopping.domain.Product
-import woowacourse.shopping.domain.repository.DomainProductRepository
-
-class ProductRepository(
- private val localProductDataSource: ProductDataSource.Local,
-) : DomainProductRepository {
- override fun getPartially(size: Int, startId: Int): List =
- localProductDataSource.getPartially(size, startId).map { it.toDomain() }
-}
diff --git a/app/src/main/java/woowacourse/shopping/data/repository/ProductRepositoryImpl.kt b/app/src/main/java/woowacourse/shopping/data/repository/ProductRepositoryImpl.kt
new file mode 100644
index 000000000..dbc351d0f
--- /dev/null
+++ b/app/src/main/java/woowacourse/shopping/data/repository/ProductRepositoryImpl.kt
@@ -0,0 +1,18 @@
+package woowacourse.shopping.data.repository
+
+import woowacourse.shopping.data.datasource.product.ProductDataSource
+import woowacourse.shopping.data.mapper.toData
+import woowacourse.shopping.data.mapper.toDomain
+import woowacourse.shopping.domain.model.Product
+import woowacourse.shopping.domain.model.page.Page
+import woowacourse.shopping.domain.repository.ProductRepository
+
+class ProductRepositoryImpl(
+ private val remoteProductDataSource: ProductDataSource.Remote,
+) : ProductRepository {
+ override fun getProductByPage(page: Page): List =
+ remoteProductDataSource.getProductByPage(page.toData()).map { it.toDomain() }
+
+ override fun findProductById(id: Int): Product? =
+ remoteProductDataSource.findProductById(id)?.toDomain()
+}
diff --git a/app/src/main/java/woowacourse/shopping/data/repository/RecentProductRepository.kt b/app/src/main/java/woowacourse/shopping/data/repository/RecentProductRepository.kt
deleted file mode 100644
index 74f316701..000000000
--- a/app/src/main/java/woowacourse/shopping/data/repository/RecentProductRepository.kt
+++ /dev/null
@@ -1,17 +0,0 @@
-package woowacourse.shopping.data.repository
-
-import woowacourse.shopping.data.datasource.recentproduct.RecentProductDataSource
-import woowacourse.shopping.data.mapper.toData
-import woowacourse.shopping.data.mapper.toDomain
-import woowacourse.shopping.domain.RecentProduct
-import woowacourse.shopping.domain.repository.DomainRecentProductRepository
-
-class RecentProductRepository(private val localRecentProductDataSource: RecentProductDataSource.Local) :
- DomainRecentProductRepository {
- override fun add(recentProduct: RecentProduct) {
- localRecentProductDataSource.add(recentProduct.toData())
- }
-
- override fun getPartially(size: Int): List =
- localRecentProductDataSource.getPartially(size).map { it.toDomain() }
-}
diff --git a/app/src/main/java/woowacourse/shopping/data/repository/RecentProductRepositoryImpl.kt b/app/src/main/java/woowacourse/shopping/data/repository/RecentProductRepositoryImpl.kt
new file mode 100644
index 000000000..4cb88e55e
--- /dev/null
+++ b/app/src/main/java/woowacourse/shopping/data/repository/RecentProductRepositoryImpl.kt
@@ -0,0 +1,20 @@
+package woowacourse.shopping.data.repository
+
+import woowacourse.shopping.data.datasource.recentproduct.RecentProductDataSource
+import woowacourse.shopping.data.mapper.toData
+import woowacourse.shopping.data.mapper.toDomain
+import woowacourse.shopping.domain.model.RecentProduct
+import woowacourse.shopping.domain.model.RecentProducts
+import woowacourse.shopping.domain.repository.RecentProductRepository
+
+class RecentProductRepositoryImpl(
+ private val localRecentProductDataSource: RecentProductDataSource.Local,
+) : RecentProductRepository {
+
+ override fun add(recentProduct: RecentProduct) {
+ localRecentProductDataSource.add(recentProduct.toData())
+ }
+
+ override fun getPartially(size: Int): RecentProducts =
+ RecentProducts(localRecentProductDataSource.getPartially(size).toDomain())
+}
diff --git a/app/src/main/java/woowacourse/shopping/mapper/BasketMapper.kt b/app/src/main/java/woowacourse/shopping/mapper/BasketMapper.kt
new file mode 100644
index 000000000..7301048e6
--- /dev/null
+++ b/app/src/main/java/woowacourse/shopping/mapper/BasketMapper.kt
@@ -0,0 +1,12 @@
+package woowacourse.shopping.mapper
+
+import woowacourse.shopping.domain.model.DomainCart
+import woowacourse.shopping.model.UiCart
+
+fun UiCart.toDomain(): DomainCart = DomainCart(
+ items = cartProducts.map { it.toDomain() },
+)
+
+fun DomainCart.toUi(): UiCart = UiCart(
+ cartProducts = items.map { it.toUi() },
+)
diff --git a/app/src/main/java/woowacourse/shopping/mapper/BasketProductMapper.kt b/app/src/main/java/woowacourse/shopping/mapper/BasketProductMapper.kt
new file mode 100644
index 000000000..a67a1ef0b
--- /dev/null
+++ b/app/src/main/java/woowacourse/shopping/mapper/BasketProductMapper.kt
@@ -0,0 +1,21 @@
+package woowacourse.shopping.mapper
+
+import woowacourse.shopping.domain.model.DomainCartProduct
+import woowacourse.shopping.model.UiCartProduct
+
+fun UiCartProduct.toDomain(): DomainCartProduct = DomainCartProduct(
+ id = id,
+ product = product.toDomain(),
+ selectedCount = selectedCount.toDomain(),
+ isChecked = isChecked,
+)
+
+fun DomainCartProduct.toUi(): UiCartProduct = UiCartProduct(
+ id = id,
+ product = product.toUi(),
+ selectedCount = selectedCount.toUi(),
+ isChecked = isChecked,
+)
+
+fun List.toUi(): List =
+ map { cartProduct -> cartProduct.toUi() }
diff --git a/app/src/main/java/woowacourse/shopping/mapper/PageMapper.kt b/app/src/main/java/woowacourse/shopping/mapper/PageMapper.kt
new file mode 100644
index 000000000..42a868b2a
--- /dev/null
+++ b/app/src/main/java/woowacourse/shopping/mapper/PageMapper.kt
@@ -0,0 +1,6 @@
+package woowacourse.shopping.mapper
+
+import woowacourse.shopping.domain.model.page.DomainPage
+import woowacourse.shopping.model.UiPage
+
+fun DomainPage.toUi(): UiPage = UiPage(value = value)
diff --git a/app/src/main/java/woowacourse/shopping/mapper/PageNumberMapper.kt b/app/src/main/java/woowacourse/shopping/mapper/PageNumberMapper.kt
deleted file mode 100644
index 0f6fbde5f..000000000
--- a/app/src/main/java/woowacourse/shopping/mapper/PageNumberMapper.kt
+++ /dev/null
@@ -1,10 +0,0 @@
-package woowacourse.shopping.mapper
-
-import woowacourse.shopping.domain.DomainPageNumber
-import woowacourse.shopping.model.UiPageNumber
-
-fun UiPageNumber.toDomain(sizePerPage: Int): DomainPageNumber =
- DomainPageNumber(value = value, sizePerPage = sizePerPage)
-
-fun DomainPageNumber.toUi(): UiPageNumber =
- UiPageNumber(value = value)
diff --git a/app/src/main/java/woowacourse/shopping/mapper/PriceMapper.kt b/app/src/main/java/woowacourse/shopping/mapper/PriceMapper.kt
index 6a9417c3f..dcb3a9691 100644
--- a/app/src/main/java/woowacourse/shopping/mapper/PriceMapper.kt
+++ b/app/src/main/java/woowacourse/shopping/mapper/PriceMapper.kt
@@ -1,6 +1,6 @@
package woowacourse.shopping.mapper
-import woowacourse.shopping.domain.Price
+import woowacourse.shopping.domain.model.Price
import woowacourse.shopping.model.UiPrice
fun UiPrice.toDomain(): Price =
diff --git a/app/src/main/java/woowacourse/shopping/mapper/ProductCountMapper.kt b/app/src/main/java/woowacourse/shopping/mapper/ProductCountMapper.kt
new file mode 100644
index 000000000..70dd8a8a3
--- /dev/null
+++ b/app/src/main/java/woowacourse/shopping/mapper/ProductCountMapper.kt
@@ -0,0 +1,10 @@
+package woowacourse.shopping.mapper
+
+import woowacourse.shopping.domain.model.ProductCount
+import woowacourse.shopping.model.UiProductCount
+
+fun UiProductCount.toDomain(): ProductCount =
+ ProductCount(value)
+
+fun ProductCount.toUi(): UiProductCount =
+ UiProductCount(value)
diff --git a/app/src/main/java/woowacourse/shopping/mapper/ProductMapper.kt b/app/src/main/java/woowacourse/shopping/mapper/ProductMapper.kt
index 76b5c7d6f..c2cc996c4 100644
--- a/app/src/main/java/woowacourse/shopping/mapper/ProductMapper.kt
+++ b/app/src/main/java/woowacourse/shopping/mapper/ProductMapper.kt
@@ -1,10 +1,20 @@
package woowacourse.shopping.mapper
-import woowacourse.shopping.domain.Product
+import woowacourse.shopping.domain.model.Product
import woowacourse.shopping.model.UiProduct
fun UiProduct.toDomain(): Product =
- Product(id = id, name = name, price = price.toDomain(), imageUrl = imageUrl)
+ Product(
+ id = id,
+ name = name,
+ price = price.toDomain(),
+ imageUrl = imageUrl,
+ )
fun Product.toUi(): UiProduct =
- UiProduct(id = id, name = name, price = price.toUi(), imageUrl = imageUrl)
+ UiProduct(
+ id = id,
+ name = name,
+ price = price.toUi(),
+ imageUrl = imageUrl,
+ )
diff --git a/app/src/main/java/woowacourse/shopping/mapper/ProductsMapper.kt b/app/src/main/java/woowacourse/shopping/mapper/ProductsMapper.kt
deleted file mode 100644
index bd7455f4c..000000000
--- a/app/src/main/java/woowacourse/shopping/mapper/ProductsMapper.kt
+++ /dev/null
@@ -1,13 +0,0 @@
-package woowacourse.shopping.mapper
-
-import woowacourse.shopping.domain.DomainProducts
-import woowacourse.shopping.model.UiProducts
-
-fun UiProducts.toDomain(loadUnit: Int): DomainProducts = DomainProducts(
- items = getItems().map { it.toDomain() },
- loadUnit = loadUnit,
-)
-
-fun DomainProducts.toUi(): UiProducts = UiProducts(
- items = getItems().map { it.toUi() },
-)
diff --git a/app/src/main/java/woowacourse/shopping/mapper/RecentProductMapper.kt b/app/src/main/java/woowacourse/shopping/mapper/RecentProductMapper.kt
index 554b9d5ca..9d7e0df70 100644
--- a/app/src/main/java/woowacourse/shopping/mapper/RecentProductMapper.kt
+++ b/app/src/main/java/woowacourse/shopping/mapper/RecentProductMapper.kt
@@ -1,6 +1,6 @@
package woowacourse.shopping.mapper
-import woowacourse.shopping.domain.RecentProduct
+import woowacourse.shopping.domain.model.RecentProduct
import woowacourse.shopping.model.UiRecentProduct
fun UiRecentProduct.toDomain(): RecentProduct =
@@ -8,3 +8,6 @@ fun UiRecentProduct.toDomain(): RecentProduct =
fun RecentProduct.toUi(): UiRecentProduct =
UiRecentProduct(id = id, product = product.toUi())
+
+fun List.toUi(): List =
+ map { recentProduct -> recentProduct.toUi() }
diff --git a/app/src/main/java/woowacourse/shopping/model/Cart.kt b/app/src/main/java/woowacourse/shopping/model/Cart.kt
new file mode 100644
index 000000000..6cc9f9228
--- /dev/null
+++ b/app/src/main/java/woowacourse/shopping/model/Cart.kt
@@ -0,0 +1,7 @@
+package woowacourse.shopping.model
+
+typealias UiCart = Cart
+
+class Cart(
+ val cartProducts: List = emptyList(),
+)
diff --git a/app/src/main/java/woowacourse/shopping/model/CartProduct.kt b/app/src/main/java/woowacourse/shopping/model/CartProduct.kt
new file mode 100644
index 000000000..22cc3f43d
--- /dev/null
+++ b/app/src/main/java/woowacourse/shopping/model/CartProduct.kt
@@ -0,0 +1,13 @@
+package woowacourse.shopping.model
+
+typealias UiCartProduct = CartProduct
+
+data class CartProduct(
+ val id: Int,
+ val product: UiProduct,
+ val selectedCount: UiProductCount = UiProductCount(0),
+ val isChecked: Boolean,
+) {
+ val shouldShowCounter: Boolean
+ get() = selectedCount.value > 0
+}
diff --git a/app/src/main/java/woowacourse/shopping/model/PageNumber.kt b/app/src/main/java/woowacourse/shopping/model/Page.kt
similarity index 52%
rename from app/src/main/java/woowacourse/shopping/model/PageNumber.kt
rename to app/src/main/java/woowacourse/shopping/model/Page.kt
index 193783a7f..05236561c 100644
--- a/app/src/main/java/woowacourse/shopping/model/PageNumber.kt
+++ b/app/src/main/java/woowacourse/shopping/model/Page.kt
@@ -1,7 +1,7 @@
package woowacourse.shopping.model
-typealias UiPageNumber = PageNumber
+typealias UiPage = Page
-data class PageNumber(val value: Int) {
+data class Page(val value: Int) {
fun toText(): String = value.toString()
}
diff --git a/app/src/main/java/woowacourse/shopping/model/PageMapper.kt b/app/src/main/java/woowacourse/shopping/model/PageMapper.kt
deleted file mode 100644
index 955d1af5a..000000000
--- a/app/src/main/java/woowacourse/shopping/model/PageMapper.kt
+++ /dev/null
@@ -1,7 +0,0 @@
-package woowacourse.shopping.model
-
-import woowacourse.shopping.domain.DomainPageNumber
-
-fun UiPageNumber.toDomain(): DomainPageNumber = DomainPageNumber(value = value)
-
-fun DomainPageNumber.toUi(): UiPageNumber = UiPageNumber(value = value)
diff --git a/app/src/main/java/woowacourse/shopping/model/Price.kt b/app/src/main/java/woowacourse/shopping/model/Price.kt
index 95bcd906d..8f8b07027 100644
--- a/app/src/main/java/woowacourse/shopping/model/Price.kt
+++ b/app/src/main/java/woowacourse/shopping/model/Price.kt
@@ -1,7 +1,7 @@
package woowacourse.shopping.model
import android.os.Parcelable
-import kotlinx.android.parcel.Parcelize
+import kotlinx.parcelize.Parcelize
typealias UiPrice = Price
diff --git a/app/src/main/java/woowacourse/shopping/model/ProductCount.kt b/app/src/main/java/woowacourse/shopping/model/ProductCount.kt
new file mode 100644
index 000000000..2baf24d64
--- /dev/null
+++ b/app/src/main/java/woowacourse/shopping/model/ProductCount.kt
@@ -0,0 +1,17 @@
+package woowacourse.shopping.model
+
+import android.os.Parcelable
+import android.view.View.GONE
+import android.view.View.VISIBLE
+import kotlinx.parcelize.Parcelize
+
+typealias UiProductCount = ProductCount
+
+@Parcelize
+data class ProductCount(val value: Int) : Parcelable {
+ fun toText(): String =
+ if (value > 99) "99" else value.toString()
+
+ fun getVisibility(): Int =
+ if (value == 0) GONE else VISIBLE
+}
diff --git a/app/src/main/java/woowacourse/shopping/model/Products.kt b/app/src/main/java/woowacourse/shopping/model/Products.kt
deleted file mode 100644
index 9fbd6ef83..000000000
--- a/app/src/main/java/woowacourse/shopping/model/Products.kt
+++ /dev/null
@@ -1,13 +0,0 @@
-package woowacourse.shopping.model
-
-import android.os.Parcelable
-import kotlinx.parcelize.Parcelize
-
-typealias UiProducts = Products
-
-@Parcelize
-class Products(
- private val items: List = emptyList(),
-) : Parcelable {
- fun getItems(): List = items
-}
diff --git a/app/src/main/java/woowacourse/shopping/server/Mock.kt b/app/src/main/java/woowacourse/shopping/server/Mock.kt
new file mode 100644
index 000000000..b4cba17dc
--- /dev/null
+++ b/app/src/main/java/woowacourse/shopping/server/Mock.kt
@@ -0,0 +1,22 @@
+package woowacourse.shopping.server
+
+fun getProducts(startId: Int, offset: Int): String = List(offset) { id ->
+ """
+ {
+ "id": ${startId + id},
+ "name": "상품${startId + id}",
+ "imageUrl": "https://mediahub.seoul.go.kr/uploads/2016/09/952e8925ec41cc06e6164d695d776e51.jpg",
+ "price": 1000
+ }
+ """
+}.joinToString(",", prefix = "[", postfix = "]").trimIndent()
+
+
+fun getProductById(productId: Int): String = """
+ {
+ "id": ${productId},
+ "name": "상품${productId}",
+ "imageUrl": "https://mediahub.seoul.go.kr/uploads/2016/09/952e8925ec41cc06e6164d695d776e51.jpg",
+ "price": 1000
+ }
+""".trimIndent()
diff --git a/app/src/main/java/woowacourse/shopping/server/ServerContract.kt b/app/src/main/java/woowacourse/shopping/server/ServerContract.kt
new file mode 100644
index 000000000..b607396dd
--- /dev/null
+++ b/app/src/main/java/woowacourse/shopping/server/ServerContract.kt
@@ -0,0 +1,6 @@
+package woowacourse.shopping.server
+
+internal const val PORT = "8080"
+internal const val BASE_URL = "http://localhost:$PORT"
+
+internal const val GET = "GET"
diff --git a/app/src/main/java/woowacourse/shopping/server/ShoppingMockWebServer.kt b/app/src/main/java/woowacourse/shopping/server/ShoppingMockWebServer.kt
new file mode 100644
index 000000000..6a7cccf9c
--- /dev/null
+++ b/app/src/main/java/woowacourse/shopping/server/ShoppingMockWebServer.kt
@@ -0,0 +1,55 @@
+package woowacourse.shopping.server
+
+import okhttp3.mockwebserver.Dispatcher
+import okhttp3.mockwebserver.MockResponse
+import okhttp3.mockwebserver.MockWebServer
+import okhttp3.mockwebserver.RecordedRequest
+import woowacourse.shopping.util.extension.parseQueryString
+
+class ShoppingMockWebServer : Thread() {
+ private val mockWebServer: MockWebServer = MockWebServer()
+ private lateinit var _baseUrl: String
+ val baseUrl: String get() = _baseUrl
+
+ override fun run() {
+ super.run()
+ mockWebServer.url("/")
+ mockWebServer.dispatcher = getDispatcher()
+ _baseUrl = "http://localhost:${mockWebServer.port}"
+ }
+
+ private fun getDispatcher(): Dispatcher = object : Dispatcher() {
+ override fun dispatch(request: RecordedRequest): MockResponse {
+ return when (request.method) {
+ GET -> {
+ val path = request.path ?: return MockResponse().setResponseCode(404)
+ return processGet(path)
+ }
+
+ else -> MockResponse().setResponseCode(404)
+ }
+ }
+ }
+
+ private fun processGet(path: String): MockResponse = when {
+ path.startsWith("/products") && path.contains("productId") -> {
+ val productId = path.parseQueryString()["productId"]?.toInt() ?: 1
+ MockResponse()
+ .setHeader("Content-Type", "application/json")
+ .setResponseCode(200)
+ .setBody(getProductById(productId))
+ }
+
+ path.startsWith("/products") -> {
+ val queryStrings = path.parseQueryString()
+ val startId = queryStrings["start"]?.toInt() ?: 1
+ val offset = queryStrings["count"]?.toInt() ?: 20
+ MockResponse()
+ .setHeader("Content-Type", "application/json")
+ .setResponseCode(200)
+ .setBody(getProducts(startId, offset))
+ }
+
+ else -> MockResponse().setResponseCode(404)
+ }
+}
diff --git a/app/src/main/java/woowacourse/shopping/ui/basket/BasketActivity.kt b/app/src/main/java/woowacourse/shopping/ui/basket/BasketActivity.kt
deleted file mode 100644
index b0edd44b9..000000000
--- a/app/src/main/java/woowacourse/shopping/ui/basket/BasketActivity.kt
+++ /dev/null
@@ -1,56 +0,0 @@
-package woowacourse.shopping.ui.basket
-
-import android.content.Context
-import android.content.Intent
-import android.os.Bundle
-import androidx.appcompat.app.AppCompatActivity
-import androidx.databinding.DataBindingUtil
-import woowacourse.shopping.R
-import woowacourse.shopping.data.database.ShoppingDatabase
-import woowacourse.shopping.databinding.ActivityBasketBinding
-import woowacourse.shopping.model.UiPageNumber
-import woowacourse.shopping.model.UiProduct
-import woowacourse.shopping.ui.basket.BasketContract.Presenter
-import woowacourse.shopping.ui.basket.BasketContract.View
-import woowacourse.shopping.ui.basket.recyclerview.adapter.BasketAdapter
-import woowacourse.shopping.util.factory.createBasketPresenter
-
-class BasketActivity : AppCompatActivity(), View {
- private val shoppingDatabase by lazy { ShoppingDatabase(this) }
- override val presenter: Presenter by lazy { createBasketPresenter(this, shoppingDatabase) }
- private lateinit var binding: ActivityBasketBinding
-
-
- override fun onCreate(savedInstanceState: Bundle?) {
- super.onCreate(savedInstanceState)
- binding = DataBindingUtil.setContentView(this, R.layout.activity_basket)
- binding.presenter = presenter
- binding.adapter = BasketAdapter(presenter::removeBasketProduct)
- }
-
- override fun updateBasket(products: List) {
- binding.adapter?.submitList(products)
- }
-
- override fun updateNavigatorEnabled(previous: Boolean, next: Boolean) {
- binding.previousButton.isEnabled = previous
- binding.nextButton.isEnabled = next
- }
-
- override fun updatePageNumber(page: UiPageNumber) {
- binding.pageNumberTextView.text = page.toText()
- }
-
- override fun closeScreen() {
- finish()
- }
-
- override fun onDestroy() {
- super.onDestroy()
- shoppingDatabase.close()
- }
-
- companion object {
- fun getIntent(context: Context) = Intent(context, BasketActivity::class.java)
- }
-}
diff --git a/app/src/main/java/woowacourse/shopping/ui/basket/BasketContract.kt b/app/src/main/java/woowacourse/shopping/ui/basket/BasketContract.kt
deleted file mode 100644
index a1053abcb..000000000
--- a/app/src/main/java/woowacourse/shopping/ui/basket/BasketContract.kt
+++ /dev/null
@@ -1,23 +0,0 @@
-package woowacourse.shopping.ui.basket
-
-import woowacourse.shopping.model.PageNumber
-import woowacourse.shopping.model.UiProduct
-
-interface BasketContract {
- interface View {
- val presenter: Presenter
-
- fun updateBasket(products: List)
- fun updateNavigatorEnabled(previous: Boolean, next: Boolean)
- fun closeScreen()
- fun updatePageNumber(page: PageNumber)
- }
-
- abstract class Presenter(protected val view: View) {
- abstract fun fetchBasket()
- abstract fun fetchPrevious()
- abstract fun fetchNext()
- abstract fun removeBasketProduct(product: UiProduct)
- abstract fun closeScreen()
- }
-}
diff --git a/app/src/main/java/woowacourse/shopping/ui/basket/BasketPresenter.kt b/app/src/main/java/woowacourse/shopping/ui/basket/BasketPresenter.kt
deleted file mode 100644
index 3f70514b8..000000000
--- a/app/src/main/java/woowacourse/shopping/ui/basket/BasketPresenter.kt
+++ /dev/null
@@ -1,50 +0,0 @@
-package woowacourse.shopping.ui.basket
-
-import woowacourse.shopping.domain.PageNumber
-import woowacourse.shopping.domain.Products
-import woowacourse.shopping.domain.repository.BasketRepository
-import woowacourse.shopping.mapper.toDomain
-import woowacourse.shopping.mapper.toUi
-import woowacourse.shopping.model.UiProduct
-import woowacourse.shopping.ui.basket.BasketContract.Presenter
-import woowacourse.shopping.ui.basket.BasketContract.View
-
-class BasketPresenter(
- view: View,
- private val basketRepository: BasketRepository,
- private var products: Products = Products(loadUnit = BASKET_PAGING_SIZE),
- private var currentPage: PageNumber = PageNumber(),
-) : Presenter(view) {
-
- override fun fetchBasket() {
- val currentProducts = basketRepository.getPartially(currentPage)
- products = products.copy(currentProducts)
-
- view.updateBasket(products.getItemsByUnit().map { it.toUi() })
- view.updateNavigatorEnabled(currentPage.hasPrevious(), products.canLoadMore())
- view.updatePageNumber(currentPage.toUi())
- }
-
- override fun fetchNext() {
- currentPage++
- fetchBasket()
- }
-
- override fun fetchPrevious() {
- currentPage--
- fetchBasket()
- }
-
- override fun removeBasketProduct(product: UiProduct) {
- basketRepository.remove(product.toDomain())
- fetchBasket()
- }
-
- override fun closeScreen() {
- view.closeScreen()
- }
-
- companion object {
- private const val BASKET_PAGING_SIZE = 5
- }
-}
diff --git a/app/src/main/java/woowacourse/shopping/ui/basket/recyclerview/adapter/BasketAdapter.kt b/app/src/main/java/woowacourse/shopping/ui/basket/recyclerview/adapter/BasketAdapter.kt
deleted file mode 100644
index c0abd40aa..000000000
--- a/app/src/main/java/woowacourse/shopping/ui/basket/recyclerview/adapter/BasketAdapter.kt
+++ /dev/null
@@ -1,28 +0,0 @@
-package woowacourse.shopping.ui.basket.recyclerview.adapter
-
-import android.view.ViewGroup
-import androidx.recyclerview.widget.DiffUtil
-import androidx.recyclerview.widget.ListAdapter
-import woowacourse.shopping.model.UiProduct
-
-class BasketAdapter(private val onDeleteClick: (UiProduct) -> Unit) :
- ListAdapter(basketDiffUtil) {
-
- override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): BasketViewHolder =
- BasketViewHolder(parent) { pos -> onDeleteClick(currentList[pos]) }
-
-
- override fun onBindViewHolder(holder: BasketViewHolder, position: Int) {
- holder.bind(getItem(position))
- }
-
- companion object {
- private val basketDiffUtil = object : DiffUtil.ItemCallback() {
- override fun areItemsTheSame(oldItem: UiProduct, newItem: UiProduct): Boolean =
- oldItem.id == newItem.id
-
- override fun areContentsTheSame(oldItem: UiProduct, newItem: UiProduct): Boolean =
- oldItem == newItem
- }
- }
-}
diff --git a/app/src/main/java/woowacourse/shopping/ui/basket/recyclerview/adapter/BasketViewHolder.kt b/app/src/main/java/woowacourse/shopping/ui/basket/recyclerview/adapter/BasketViewHolder.kt
deleted file mode 100644
index 3af1a28a6..000000000
--- a/app/src/main/java/woowacourse/shopping/ui/basket/recyclerview/adapter/BasketViewHolder.kt
+++ /dev/null
@@ -1,24 +0,0 @@
-package woowacourse.shopping.ui.basket.recyclerview.adapter
-
-import android.view.LayoutInflater
-import android.view.ViewGroup
-import androidx.recyclerview.widget.RecyclerView.ViewHolder
-import woowacourse.shopping.R
-import woowacourse.shopping.databinding.ItemBasketBinding
-import woowacourse.shopping.model.UiProduct
-
-class BasketViewHolder(parent: ViewGroup, onItemClick: (Int) -> Unit) : ViewHolder(
- LayoutInflater.from(parent.context).inflate(R.layout.item_basket, parent, false)
-) {
- private val binding = ItemBasketBinding.bind(itemView)
-
- init {
- binding.closeButton.setOnClickListener {
- onItemClick(bindingAdapterPosition)
- }
- }
-
- fun bind(item: UiProduct) {
- binding.product = item
- }
-}
diff --git a/app/src/main/java/woowacourse/shopping/ui/cart/CartActivity.kt b/app/src/main/java/woowacourse/shopping/ui/cart/CartActivity.kt
new file mode 100644
index 000000000..97f7eafe6
--- /dev/null
+++ b/app/src/main/java/woowacourse/shopping/ui/cart/CartActivity.kt
@@ -0,0 +1,78 @@
+package woowacourse.shopping.ui.cart
+
+import android.content.Context
+import android.content.Intent
+import android.os.Bundle
+import androidx.appcompat.app.AppCompatActivity
+import woowacourse.shopping.R
+import woowacourse.shopping.databinding.ActivityCartBinding
+import woowacourse.shopping.model.UiCartProduct
+import woowacourse.shopping.model.UiPage
+import woowacourse.shopping.model.UiProduct
+import woowacourse.shopping.ui.cart.CartContract.View
+import woowacourse.shopping.ui.cart.listener.CartClickListener
+import woowacourse.shopping.ui.cart.recyclerview.adapter.CartAdapter
+import woowacourse.shopping.util.extension.setContentView
+import woowacourse.shopping.util.extension.showToast
+import woowacourse.shopping.util.inject.inject
+
+class CartActivity : AppCompatActivity(), View, CartClickListener {
+ private val presenter: CartPresenter by lazy { inject(this, this) }
+ private lateinit var binding: ActivityCartBinding
+
+ override fun onCreate(savedInstanceState: Bundle?) {
+ super.onCreate(savedInstanceState)
+ binding = ActivityCartBinding.inflate(layoutInflater).setContentView(this)
+ binding.lifecycleOwner = this
+ binding.presenter = presenter
+ binding.adapter = CartAdapter(this)
+ presenter.fetchCart(1)
+ }
+
+ override fun updateCart(cartProducts: List) {
+ binding.adapter?.submitList(cartProducts)
+ }
+
+ override fun updateNavigatorEnabled(previousEnabled: Boolean, nextEnabled: Boolean) {
+ binding.previousButton.isEnabled = previousEnabled
+ binding.nextButton.isEnabled = nextEnabled
+ }
+
+ override fun updatePageNumber(page: UiPage) {
+ binding.pageNumberTextView.text = page.toText()
+ }
+
+ override fun updateTotalPrice(totalPrice: Int) {
+ binding.totalPriceTextView.text = getString(R.string.price_format, totalPrice)
+ }
+
+ override fun onCountChanged(product: UiProduct, count: Int, isIncreased: Boolean) {
+ presenter.changeProductCount(product, count, isIncreased)
+ }
+
+ override fun onCheckStateChanged(product: UiProduct, isChecked: Boolean) {
+ presenter.changeProductSelectState(product, isChecked)
+ }
+
+ override fun onDeleteClick(product: UiProduct) {
+ presenter.removeProduct(product)
+ }
+
+ override fun showOrderComplete(productCount: Int) {
+ showToast(getString(R.string.order_success_message, productCount))
+ navigateToHome()
+ }
+
+ override fun showOrderFailed() {
+ showToast(getString(R.string.order_failed_message))
+ }
+
+ override fun navigateToHome() {
+ setResult(RESULT_OK)
+ finish()
+ }
+
+ companion object {
+ fun getIntent(context: Context) = Intent(context, CartActivity::class.java)
+ }
+}
diff --git a/app/src/main/java/woowacourse/shopping/ui/cart/CartContract.kt b/app/src/main/java/woowacourse/shopping/ui/cart/CartContract.kt
new file mode 100644
index 000000000..0952e4b5e
--- /dev/null
+++ b/app/src/main/java/woowacourse/shopping/ui/cart/CartContract.kt
@@ -0,0 +1,27 @@
+package woowacourse.shopping.ui.cart
+
+import woowacourse.shopping.model.Page
+import woowacourse.shopping.model.UiCartProduct
+import woowacourse.shopping.model.UiProduct
+
+interface CartContract {
+ interface View {
+ fun updateCart(cartProducts: List)
+ fun updateNavigatorEnabled(previousEnabled: Boolean, nextEnabled: Boolean)
+ fun updatePageNumber(page: Page)
+ fun updateTotalPrice(totalPrice: Int)
+ fun showOrderComplete(productCount: Int)
+ fun showOrderFailed()
+ fun navigateToHome()
+ }
+
+ abstract class Presenter(protected val view: View) {
+ abstract fun fetchCart(page: Int)
+ abstract fun changeProductCount(product: UiProduct, count: Int, increase: Boolean)
+ abstract fun changeProductSelectState(product: UiProduct, isSelect: Boolean)
+ abstract fun toggleAllCheckState()
+ abstract fun removeProduct(product: UiProduct)
+ abstract fun order()
+ abstract fun navigateToHome()
+ }
+}
diff --git a/app/src/main/java/woowacourse/shopping/ui/cart/CartPresenter.kt b/app/src/main/java/woowacourse/shopping/ui/cart/CartPresenter.kt
new file mode 100644
index 000000000..49b6f1ca0
--- /dev/null
+++ b/app/src/main/java/woowacourse/shopping/ui/cart/CartPresenter.kt
@@ -0,0 +1,106 @@
+package woowacourse.shopping.ui.cart
+
+import androidx.lifecycle.LiveData
+import androidx.lifecycle.MutableLiveData
+import androidx.lifecycle.Transformations
+import woowacourse.shopping.domain.model.Cart
+import woowacourse.shopping.domain.model.CartProduct
+import woowacourse.shopping.domain.model.ProductCount
+import woowacourse.shopping.domain.model.page.Page
+import woowacourse.shopping.domain.model.page.Pagination
+import woowacourse.shopping.domain.repository.CartRepository
+import woowacourse.shopping.domain.repository.ProductRepository
+import woowacourse.shopping.mapper.toDomain
+import woowacourse.shopping.mapper.toUi
+import woowacourse.shopping.model.UiProduct
+import woowacourse.shopping.ui.cart.CartContract.Presenter
+import woowacourse.shopping.ui.cart.CartContract.View
+
+class CartPresenter(
+ view: View,
+ private val productRepository: ProductRepository,
+ private val cartRepository: CartRepository,
+ cartSize: Int = 5,
+) : Presenter(view) {
+ private var cart: Cart = Cart(minProductSize = 1)
+ private var currentPage: Page = Pagination(sizePerPage = cartSize)
+
+ private val _totalCheckSize = MutableLiveData(cartRepository.getCheckedProductCount())
+ val totalCheckSize: LiveData get() = _totalCheckSize
+
+ private val _pageCheckSize = MutableLiveData(currentPage.getCheckedProductSize(cart))
+ val isAllChecked: LiveData = Transformations.map(_pageCheckSize) { pageCheckSize ->
+ pageCheckSize == currentPage.takeItems(cart).size
+ }
+
+ override fun fetchCart(page: Int) {
+ currentPage = currentPage.update(page)
+ cart = cart.update(loadCartProducts())
+
+ view.updateNavigatorEnabled(currentPage.hasPrevious(), currentPage.hasNext(cart))
+ view.updatePageNumber(currentPage.toUi())
+ fetchView()
+ }
+
+ private fun loadCartProducts(): List =
+ cartRepository.getAllCartEntities().mapNotNull {
+ val product = productRepository.findProductById(it.productId)
+ product?.run { CartProduct(it.id, this, ProductCount(it.count), it.checked) }
+ }
+
+ override fun changeProductCount(product: UiProduct, count: Int, increase: Boolean) {
+ updateCart(changeCount(product, count, increase))
+ }
+
+ private fun changeCount(product: UiProduct, count: Int, isInc: Boolean): Cart = when (isInc) {
+ true -> cart.increaseProductCount(product.toDomain(), count)
+ false -> cart.decreaseProductCount(product.toDomain(), count)
+ }
+
+ override fun changeProductSelectState(product: UiProduct, isSelect: Boolean) {
+ updateCart(changeSelectState(product, isSelect))
+ }
+
+ private fun changeSelectState(product: UiProduct, isSelect: Boolean): Cart =
+ if (isSelect) cart.select(product.toDomain()) else cart.unselect(product.toDomain())
+
+ override fun toggleAllCheckState() {
+ updateCart(
+ if (isAllChecked.value == true) {
+ cart.unselectAll(currentPage)
+ } else cart.selectAll(
+ currentPage
+ )
+ )
+ }
+
+ override fun removeProduct(product: UiProduct) {
+ cartRepository.deleteByProductId(product.id)
+ fetchCart(currentPage.value)
+ }
+
+ override fun order() {
+ if (_totalCheckSize.value == 0) {
+ view.showOrderFailed(); return
+ }
+ cartRepository.removeCheckedProducts()
+ view.showOrderComplete(_totalCheckSize.value ?: 0)
+ }
+
+ override fun navigateToHome() {
+ view.navigateToHome()
+ }
+
+ private fun updateCart(newCart: Cart) {
+ cart = cart.update(newCart)
+ cartRepository.update(currentPage.takeItems(cart))
+ fetchView()
+ }
+
+ private fun fetchView() {
+ _totalCheckSize.value = cartRepository.getCheckedProductCount()
+ _pageCheckSize.value = currentPage.getCheckedProductSize(cart)
+ view.updateTotalPrice(cart.getCheckedProductTotalPrice())
+ view.updateCart(currentPage.takeItems(cart).toUi())
+ }
+}
diff --git a/app/src/main/java/woowacourse/shopping/ui/cart/listener/CartClickListener.kt b/app/src/main/java/woowacourse/shopping/ui/cart/listener/CartClickListener.kt
new file mode 100644
index 000000000..7847bc1af
--- /dev/null
+++ b/app/src/main/java/woowacourse/shopping/ui/cart/listener/CartClickListener.kt
@@ -0,0 +1,9 @@
+package woowacourse.shopping.ui.cart.listener
+
+import woowacourse.shopping.model.UiProduct
+
+interface CartClickListener {
+ fun onCountChanged(product: UiProduct, count: Int, isIncreased: Boolean)
+ fun onCheckStateChanged(product: UiProduct, isChecked: Boolean)
+ fun onDeleteClick(product: UiProduct)
+}
diff --git a/app/src/main/java/woowacourse/shopping/ui/cart/recyclerview/adapter/CartAdapter.kt b/app/src/main/java/woowacourse/shopping/ui/cart/recyclerview/adapter/CartAdapter.kt
new file mode 100644
index 000000000..46c241b88
--- /dev/null
+++ b/app/src/main/java/woowacourse/shopping/ui/cart/recyclerview/adapter/CartAdapter.kt
@@ -0,0 +1,18 @@
+package woowacourse.shopping.ui.cart.recyclerview.adapter
+
+import android.view.ViewGroup
+import androidx.recyclerview.widget.ListAdapter
+import woowacourse.shopping.model.UiCartProduct
+import woowacourse.shopping.ui.cart.listener.CartClickListener
+import woowacourse.shopping.util.diffutil.CartDiffUtil
+
+class CartAdapter(
+ private val cartClickListener: CartClickListener,
+) : ListAdapter(CartDiffUtil) {
+ override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): CartViewHolder =
+ CartViewHolder(parent, cartClickListener)
+
+ override fun onBindViewHolder(holder: CartViewHolder, position: Int) {
+ holder.bind(getItem(position))
+ }
+}
diff --git a/app/src/main/java/woowacourse/shopping/ui/cart/recyclerview/adapter/CartViewHolder.kt b/app/src/main/java/woowacourse/shopping/ui/cart/recyclerview/adapter/CartViewHolder.kt
new file mode 100644
index 000000000..c82361fe1
--- /dev/null
+++ b/app/src/main/java/woowacourse/shopping/ui/cart/recyclerview/adapter/CartViewHolder.kt
@@ -0,0 +1,26 @@
+package woowacourse.shopping.ui.cart.recyclerview.adapter
+
+import android.view.LayoutInflater
+import android.view.ViewGroup
+import androidx.recyclerview.widget.RecyclerView.ViewHolder
+import woowacourse.shopping.R
+import woowacourse.shopping.databinding.ItemCartBinding
+import woowacourse.shopping.model.UiCartProduct
+import woowacourse.shopping.ui.cart.listener.CartClickListener
+
+class CartViewHolder(
+ parent: ViewGroup,
+ cartClickListener: CartClickListener,
+) : ViewHolder(
+ LayoutInflater.from(parent.context).inflate(R.layout.item_cart, parent, false)
+) {
+ private val binding = ItemCartBinding.bind(itemView)
+
+ init {
+ binding.cartClickListener = cartClickListener
+ }
+
+ fun bind(item: UiCartProduct) {
+ binding.cartProduct = item
+ }
+}
diff --git a/app/src/main/java/woowacourse/shopping/ui/detail/ProductDetailActivity.kt b/app/src/main/java/woowacourse/shopping/ui/detail/ProductDetailActivity.kt
new file mode 100644
index 000000000..529b02088
--- /dev/null
+++ b/app/src/main/java/woowacourse/shopping/ui/detail/ProductDetailActivity.kt
@@ -0,0 +1,79 @@
+package woowacourse.shopping.ui.detail
+
+import android.content.Context
+import android.content.Intent
+import android.os.Bundle
+import android.view.MenuItem
+import androidx.appcompat.app.AppCompatActivity
+import androidx.appcompat.widget.Toolbar.OnMenuItemClickListener
+import woowacourse.shopping.R
+import woowacourse.shopping.databinding.ActivityProductDetailBinding
+import woowacourse.shopping.model.UiProduct
+import woowacourse.shopping.model.UiRecentProduct
+import woowacourse.shopping.ui.detail.dialog.ProductCounterDialog
+import woowacourse.shopping.ui.detail.ProductDetailContract.Presenter
+import woowacourse.shopping.ui.detail.ProductDetailContract.View
+import woowacourse.shopping.ui.shopping.ShoppingActivity
+import woowacourse.shopping.util.extension.getParcelableExtraCompat
+import woowacourse.shopping.util.extension.setContentView
+import woowacourse.shopping.util.inject.inject
+
+class ProductDetailActivity : AppCompatActivity(), View, OnMenuItemClickListener {
+ private lateinit var binding: ActivityProductDetailBinding
+ private val presenter: Presenter by lazy {
+ inject(
+ view = this,
+ detailProduct = intent.getParcelableExtraCompat(DETAIL_PRODUCT_KEY)!!,
+ recentProduct = intent.getParcelableExtraCompat(LAST_VIEWED_PRODUCT_KEY),
+ )
+ }
+
+ override fun onCreate(savedInstanceState: Bundle?) {
+ super.onCreate(savedInstanceState)
+ binding = ActivityProductDetailBinding.inflate(layoutInflater).setContentView(this)
+ initView()
+ }
+
+ private fun initView() {
+ binding.presenter = presenter
+ binding.productDetailToolBar.setOnMenuItemClickListener(this)
+ }
+
+ override fun showProductDetail(product: UiProduct) {
+ binding.detailProduct = product
+ }
+
+ override fun showLastViewedProductDetail(lastViewedProduct: UiProduct?) {
+ binding.lastViewedProduct = lastViewedProduct
+ }
+
+ override fun showProductCounter(product: UiProduct) {
+ ProductCounterDialog(this, product, presenter::navigateToHome).show()
+ }
+
+ override fun navigateToHome(product: UiProduct, count: Int) {
+ startActivity(ShoppingActivity.getIntent(this, product, count))
+ }
+
+ override fun navigateToProductDetail(recentProduct: UiRecentProduct) {
+ startActivity(getIntent(this, recentProduct.product, null))
+ finish()
+ }
+
+ override fun onMenuItemClick(item: MenuItem): Boolean {
+ when (item.itemId) {
+ R.id.close -> finish()
+ }
+ return true
+ }
+
+ companion object {
+ private const val DETAIL_PRODUCT_KEY = "detail_product_key"
+ private const val LAST_VIEWED_PRODUCT_KEY = "last_viewed_product_key"
+
+ fun getIntent(context: Context, detail: UiProduct, recent: UiRecentProduct?): Intent =
+ Intent(context, ProductDetailActivity::class.java)
+ .putExtra(DETAIL_PRODUCT_KEY, detail)
+ .putExtra(LAST_VIEWED_PRODUCT_KEY, recent)
+ }
+}
diff --git a/app/src/main/java/woowacourse/shopping/ui/detail/ProductDetailContract.kt b/app/src/main/java/woowacourse/shopping/ui/detail/ProductDetailContract.kt
new file mode 100644
index 000000000..5618b14b2
--- /dev/null
+++ b/app/src/main/java/woowacourse/shopping/ui/detail/ProductDetailContract.kt
@@ -0,0 +1,20 @@
+package woowacourse.shopping.ui.detail
+
+import woowacourse.shopping.model.UiProduct
+import woowacourse.shopping.model.UiRecentProduct
+
+interface ProductDetailContract {
+ interface View {
+ fun showProductDetail(product: UiProduct)
+ fun showLastViewedProductDetail(lastViewedProduct: UiProduct?)
+ fun showProductCounter(product: UiProduct)
+ fun navigateToProductDetail(recentProduct: UiRecentProduct)
+ fun navigateToHome(product: UiProduct, count: Int)
+ }
+
+ abstract class Presenter(protected val view: View) {
+ abstract fun inquiryProductCounter()
+ abstract fun inquiryLastViewedProduct()
+ abstract fun navigateToHome(count: Int)
+ }
+}
diff --git a/app/src/main/java/woowacourse/shopping/ui/detail/ProductDetailPresenter.kt b/app/src/main/java/woowacourse/shopping/ui/detail/ProductDetailPresenter.kt
new file mode 100644
index 000000000..9cac331c0
--- /dev/null
+++ b/app/src/main/java/woowacourse/shopping/ui/detail/ProductDetailPresenter.kt
@@ -0,0 +1,30 @@
+package woowacourse.shopping.ui.detail
+
+import woowacourse.shopping.model.UiProduct
+import woowacourse.shopping.model.UiRecentProduct
+import woowacourse.shopping.ui.detail.ProductDetailContract.Presenter
+import woowacourse.shopping.ui.detail.ProductDetailContract.View
+
+class ProductDetailPresenter(
+ view: View,
+ private val product: UiProduct,
+ private val recentProduct: UiRecentProduct?,
+) : Presenter(view) {
+
+ init {
+ view.showProductDetail(product)
+ view.showLastViewedProductDetail(recentProduct?.product)
+ }
+
+ override fun inquiryProductCounter() {
+ view.showProductCounter(product)
+ }
+
+ override fun inquiryLastViewedProduct() {
+ recentProduct?.let { view.navigateToProductDetail(it) }
+ }
+
+ override fun navigateToHome(count: Int) {
+ view.navigateToHome(product, count)
+ }
+}
diff --git a/app/src/main/java/woowacourse/shopping/ui/detail/dialog/ProductCounterDialog.kt b/app/src/main/java/woowacourse/shopping/ui/detail/dialog/ProductCounterDialog.kt
new file mode 100644
index 000000000..98600dab0
--- /dev/null
+++ b/app/src/main/java/woowacourse/shopping/ui/detail/dialog/ProductCounterDialog.kt
@@ -0,0 +1,33 @@
+package woowacourse.shopping.ui.detail.dialog
+
+import android.app.Dialog
+import android.content.Context
+import woowacourse.shopping.R
+import woowacourse.shopping.databinding.CounterBinding
+import woowacourse.shopping.model.UiProduct
+
+class ProductCounterDialog(
+ context: Context,
+ product: UiProduct,
+ putInCart: (count: Int) -> Unit,
+) : Dialog(context) {
+
+ init {
+ val binding: CounterBinding = CounterBinding.inflate(layoutInflater)
+ setContentView(binding.root)
+ initDialogSize(context)
+ binding.product = product
+ binding.onPutInCart = { count ->
+ putInCart(count)
+ dismiss()
+ }
+ }
+
+ private fun initDialogSize(context: Context) {
+ val metrics = context.resources.displayMetrics
+ val width = (metrics.widthPixels * 0.9).toInt()
+ val height = (width * 0.4).toInt()
+ window?.setLayout(width, height)
+ window?.setBackgroundDrawableResource(R.drawable.shape_woowa_round_4_white_rect)
+ }
+}
diff --git a/app/src/main/java/woowacourse/shopping/ui/productdetail/ProductDetailActivity.kt b/app/src/main/java/woowacourse/shopping/ui/productdetail/ProductDetailActivity.kt
deleted file mode 100644
index c4a660895..000000000
--- a/app/src/main/java/woowacourse/shopping/ui/productdetail/ProductDetailActivity.kt
+++ /dev/null
@@ -1,67 +0,0 @@
-package woowacourse.shopping.ui.productdetail
-
-import android.content.Context
-import android.content.Intent
-import android.os.Bundle
-import android.view.MenuItem
-import androidx.appcompat.app.AppCompatActivity
-import androidx.appcompat.widget.Toolbar.OnMenuItemClickListener
-import androidx.databinding.DataBindingUtil
-import woowacourse.shopping.R
-import woowacourse.shopping.databinding.ActivityProductDetailBinding
-import woowacourse.shopping.model.UiProduct
-import woowacourse.shopping.ui.basket.BasketActivity
-import woowacourse.shopping.ui.productdetail.ProductDetailContract.Presenter
-import woowacourse.shopping.ui.productdetail.ProductDetailContract.View
-import woowacourse.shopping.util.extension.getParcelableExtraCompat
-import woowacourse.shopping.util.extension.showImage
-import woowacourse.shopping.util.factory.createProductDetailPresenter
-
-class ProductDetailActivity : AppCompatActivity(), View, OnMenuItemClickListener {
- private lateinit var binding: ActivityProductDetailBinding
- override val presenter: Presenter by lazy {
- createProductDetailPresenter(this, this, intent.getParcelableExtraCompat(PRODUCT_KEY)!!)
- }
-
-
- override fun onCreate(savedInstanceState: Bundle?) {
- super.onCreate(savedInstanceState)
- binding = DataBindingUtil.setContentView(this, R.layout.activity_product_detail)
- initView()
- }
-
- private fun initView() {
- binding.productDetailPresenter = presenter
- binding.productDetailToolBar.setOnMenuItemClickListener(this)
- }
-
- override fun showProductImage(imageUrl: String) {
- binding.productImageView.showImage(imageUrl)
- }
-
- override fun showProductName(name: String) {
- binding.productNameTextView.text = name
- }
-
- override fun showProductPrice(amount: Int) {
- binding.productPriceTextView.text = getString(R.string.price_format, amount)
- }
-
- override fun navigateToBasketScreen() {
- startActivity(BasketActivity.getIntent(this))
- finish()
- }
-
- override fun onMenuItemClick(item: MenuItem): Boolean {
- when (item.itemId) {
- R.id.close -> finish()
- }
- return true
- }
-
- companion object {
- private const val PRODUCT_KEY = "product_key"
- fun getIntent(context: Context, product: UiProduct): Intent =
- Intent(context, ProductDetailActivity::class.java).putExtra(PRODUCT_KEY, product)
- }
-}
diff --git a/app/src/main/java/woowacourse/shopping/ui/productdetail/ProductDetailContract.kt b/app/src/main/java/woowacourse/shopping/ui/productdetail/ProductDetailContract.kt
deleted file mode 100644
index 65add3086..000000000
--- a/app/src/main/java/woowacourse/shopping/ui/productdetail/ProductDetailContract.kt
+++ /dev/null
@@ -1,16 +0,0 @@
-package woowacourse.shopping.ui.productdetail
-
-interface ProductDetailContract {
- interface View {
- val presenter: Presenter
-
- fun showProductImage(imageUrl: String)
- fun navigateToBasketScreen()
- fun showProductName(name: String)
- fun showProductPrice(amount: Int)
- }
-
- abstract class Presenter(protected val view: View) {
- abstract fun addBasketProduct()
- }
-}
diff --git a/app/src/main/java/woowacourse/shopping/ui/productdetail/ProductDetailPresenter.kt b/app/src/main/java/woowacourse/shopping/ui/productdetail/ProductDetailPresenter.kt
deleted file mode 100644
index 0942cb4b8..000000000
--- a/app/src/main/java/woowacourse/shopping/ui/productdetail/ProductDetailPresenter.kt
+++ /dev/null
@@ -1,23 +0,0 @@
-package woowacourse.shopping.ui.productdetail
-
-import woowacourse.shopping.domain.repository.BasketRepository
-import woowacourse.shopping.mapper.toDomain
-import woowacourse.shopping.model.UiProduct
-
-class ProductDetailPresenter(
- view: ProductDetailContract.View,
- private val basketRepository: BasketRepository,
- private val product: UiProduct,
-) : ProductDetailContract.Presenter(view) {
-
- init {
- view.showProductImage(product.imageUrl)
- view.showProductName(product.name)
- view.showProductPrice(product.price.value)
- }
-
- override fun addBasketProduct() {
- basketRepository.add(product.toDomain())
- view.navigateToBasketScreen()
- }
-}
diff --git a/app/src/main/java/woowacourse/shopping/ui/shopping/ShoppingActivity.kt b/app/src/main/java/woowacourse/shopping/ui/shopping/ShoppingActivity.kt
index 542899ffc..8933443fe 100644
--- a/app/src/main/java/woowacourse/shopping/ui/shopping/ShoppingActivity.kt
+++ b/app/src/main/java/woowacourse/shopping/ui/shopping/ShoppingActivity.kt
@@ -1,58 +1,73 @@
package woowacourse.shopping.ui.shopping
+import android.content.Context
+import android.content.Intent
import android.os.Bundle
-import android.view.MenuItem
+import androidx.activity.result.contract.ActivityResultContracts.StartActivityForResult
import androidx.appcompat.app.AppCompatActivity
-import androidx.appcompat.widget.Toolbar.OnMenuItemClickListener
-import androidx.databinding.DataBindingUtil
-import androidx.recyclerview.widget.ConcatAdapter
import woowacourse.shopping.R
-import woowacourse.shopping.data.database.ShoppingDatabase
import woowacourse.shopping.databinding.ActivityShoppingBinding
+import woowacourse.shopping.model.ProductCount
+import woowacourse.shopping.model.UiCartProduct
import woowacourse.shopping.model.UiProduct
import woowacourse.shopping.model.UiRecentProduct
-import woowacourse.shopping.ui.basket.BasketActivity
-import woowacourse.shopping.ui.productdetail.ProductDetailActivity
+import woowacourse.shopping.ui.cart.CartActivity
+import woowacourse.shopping.ui.detail.ProductDetailActivity
import woowacourse.shopping.ui.shopping.ShoppingContract.Presenter
import woowacourse.shopping.ui.shopping.ShoppingContract.View
import woowacourse.shopping.ui.shopping.recyclerview.adapter.loadmore.LoadMoreAdapter
import woowacourse.shopping.ui.shopping.recyclerview.adapter.product.ProductAdapter
import woowacourse.shopping.ui.shopping.recyclerview.adapter.recentproduct.RecentProductAdapter
import woowacourse.shopping.ui.shopping.recyclerview.adapter.recentproduct.RecentProductWrapperAdapter
-import woowacourse.shopping.util.factory.createShoppingPresenter
-import woowacourse.shopping.util.isolatedViewTypeConfig
+import woowacourse.shopping.util.builder.add
+import woowacourse.shopping.util.builder.isolatedViewTypeConcatAdapter
+import woowacourse.shopping.util.extension.findItemActionView
+import woowacourse.shopping.util.extension.findTextView
+import woowacourse.shopping.util.extension.getParcelableExtraCompat
+import woowacourse.shopping.util.extension.setContentView
+import woowacourse.shopping.util.inject.inject
+import woowacourse.shopping.util.listener.ProductClickListener
+import woowacourse.shopping.widget.ProductCounterView.OnClickListener
-class ShoppingActivity : AppCompatActivity(), View, OnMenuItemClickListener {
+class ShoppingActivity : AppCompatActivity(), View, OnClickListener, ProductClickListener {
private lateinit var binding: ActivityShoppingBinding
-
- private val shoppingDatabase by lazy { ShoppingDatabase(this) }
- override val presenter: Presenter by lazy { createShoppingPresenter(this, shoppingDatabase) }
+ private val presenter: Presenter by lazy { inject(this, this) }
private val recentProductAdapter = RecentProductAdapter(presenter::inquiryRecentProductDetail)
private val recentProductWrapperAdapter = RecentProductWrapperAdapter(recentProductAdapter)
- private val productAdapter = ProductAdapter(presenter::inquiryProductDetail)
- private val loadMoreAdapter = LoadMoreAdapter(presenter::fetchProducts)
+ private val productAdapter = ProductAdapter(this, this)
+ private val loadMoreAdapter = LoadMoreAdapter(presenter::loadMoreProducts)
+
+ private val cartActivityLauncher = registerForActivityResult(StartActivityForResult()) {
+ presenter.fetchAll()
+ }
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
- binding = DataBindingUtil.setContentView(this, R.layout.activity_shopping)
+ binding = ActivityShoppingBinding.inflate(layoutInflater).setContentView(this)
initView()
}
private fun initView() {
binding.presenter = presenter
- binding.shoppingToolBar.setOnMenuItemClickListener(this)
+ initMenuClickListener()
initRecyclerView()
}
+ private fun initMenuClickListener() {
+ val cartItemView = binding.shoppingToolBar.findItemActionView(R.id.cart)
+ cartItemView?.setOnClickListener { presenter.navigateToCart() }
+ }
+
private fun initRecyclerView() {
- binding.adapter = ConcatAdapter(
- isolatedViewTypeConfig, recentProductWrapperAdapter, productAdapter, loadMoreAdapter
- )
- presenter.fetchAll()
+ binding.adapter = isolatedViewTypeConcatAdapter {
+ add(recentProductWrapperAdapter)
+ add(productAdapter)
+ add(loadMoreAdapter)
+ }
}
- override fun updateProducts(products: List) {
+ override fun updateProducts(products: List) {
productAdapter.submitList(products)
}
@@ -60,12 +75,12 @@ class ShoppingActivity : AppCompatActivity(), View, OnMenuItemClickListener {
recentProductWrapperAdapter.submitList(recentProducts)
}
- override fun showProductDetail(product: UiProduct) {
- startActivity(ProductDetailActivity.getIntent(this, product))
+ override fun navigateToProductDetail(product: UiProduct, recentProduct: UiRecentProduct?) {
+ startActivity(ProductDetailActivity.getIntent(this, product, recentProduct))
}
- override fun navigateToBasketScreen() {
- startActivity(BasketActivity.getIntent(this))
+ override fun navigateToCart() {
+ cartActivityLauncher.launch(CartActivity.getIntent(this))
}
override fun showLoadMoreButton() {
@@ -76,15 +91,45 @@ class ShoppingActivity : AppCompatActivity(), View, OnMenuItemClickListener {
loadMoreAdapter.hideButton()
}
- override fun onMenuItemClick(item: MenuItem): Boolean {
- when (item.itemId) {
- R.id.basket -> presenter.openBasket()
- }
- return true
+ override fun updateCartBadge(count: ProductCount) {
+ val cartBadgeView = binding.shoppingToolBar.findItemActionView(R.id.cart) ?: return
+ val productCountTextView = cartBadgeView.findTextView(R.id.cart_count_badge) ?: return
+
+ productCountTextView.visibility = count.getVisibility()
+ productCountTextView.text = count.toText()
}
- override fun onDestroy() {
- super.onDestroy()
- shoppingDatabase.close()
+ override fun onClickProduct(product: UiProduct) {
+ presenter.inquiryProductDetail(product)
+ }
+
+ override fun onClickProductPlus(product: UiProduct) {
+ presenter.increaseCartCount(product)
+ }
+
+ override fun onClickCounterPlus(product: UiProduct) {
+ presenter.increaseCartCount(product)
+ }
+
+ override fun onClickCounterMinus(product: UiProduct) {
+ presenter.decreaseCartCount(product)
+ }
+
+ override fun onNewIntent(intent: Intent?) {
+ super.onNewIntent(intent)
+ val product = intent?.getParcelableExtraCompat(PRODUCT_KEY) ?: return
+ val count = intent.getIntExtra(COUNT_KEY, 0)
+ presenter.increaseCartCount(product, count)
+ }
+
+ companion object {
+ private const val PRODUCT_KEY = "product_key"
+ private const val COUNT_KEY = "count_key"
+
+ fun getIntent(context: Context, product: UiProduct, count: Int): Intent =
+ Intent(context, ShoppingActivity::class.java)
+ .setFlags(Intent.FLAG_ACTIVITY_CLEAR_TOP or Intent.FLAG_ACTIVITY_SINGLE_TOP)
+ .putExtra(PRODUCT_KEY, product)
+ .putExtra(COUNT_KEY, count)
}
}
diff --git a/app/src/main/java/woowacourse/shopping/ui/shopping/ShoppingContract.kt b/app/src/main/java/woowacourse/shopping/ui/shopping/ShoppingContract.kt
index 30ad7fa97..36bc658bc 100644
--- a/app/src/main/java/woowacourse/shopping/ui/shopping/ShoppingContract.kt
+++ b/app/src/main/java/woowacourse/shopping/ui/shopping/ShoppingContract.kt
@@ -1,26 +1,29 @@
package woowacourse.shopping.ui.shopping
+import woowacourse.shopping.model.CartProduct
+import woowacourse.shopping.model.ProductCount
import woowacourse.shopping.model.UiProduct
import woowacourse.shopping.model.UiRecentProduct
interface ShoppingContract {
interface View {
- val presenter: Presenter
-
- fun updateProducts(products: List)
+ fun updateProducts(products: List)
fun updateRecentProducts(recentProducts: List)
- fun showProductDetail(product: UiProduct)
- fun navigateToBasketScreen()
+ fun navigateToProductDetail(product: UiProduct, recentProduct: UiRecentProduct?)
+ fun navigateToCart()
fun showLoadMoreButton()
fun hideLoadMoreButton()
+ fun updateCartBadge(count: ProductCount)
}
abstract class Presenter(protected val view: View) {
abstract fun fetchAll()
- abstract fun fetchProducts()
abstract fun fetchRecentProducts()
+ abstract fun loadMoreProducts()
abstract fun inquiryProductDetail(product: UiProduct)
abstract fun inquiryRecentProductDetail(recentProduct: UiRecentProduct)
- abstract fun openBasket()
+ abstract fun navigateToCart()
+ abstract fun increaseCartCount(product: UiProduct, count: Int = 1)
+ abstract fun decreaseCartCount(product: UiProduct, count: Int = 1)
}
}
diff --git a/app/src/main/java/woowacourse/shopping/ui/shopping/ShoppingPresenter.kt b/app/src/main/java/woowacourse/shopping/ui/shopping/ShoppingPresenter.kt
index 9d36a0af9..753cb7ea1 100644
--- a/app/src/main/java/woowacourse/shopping/ui/shopping/ShoppingPresenter.kt
+++ b/app/src/main/java/woowacourse/shopping/ui/shopping/ShoppingPresenter.kt
@@ -1,77 +1,111 @@
package woowacourse.shopping.ui.shopping
-import woowacourse.shopping.domain.Products
-import woowacourse.shopping.domain.RecentProduct
-import woowacourse.shopping.domain.RecentProducts
-import woowacourse.shopping.domain.repository.DomainProductRepository
-import woowacourse.shopping.domain.repository.DomainRecentProductRepository
+import woowacourse.shopping.domain.model.Cart
+import woowacourse.shopping.domain.model.DomainCartProduct
+import woowacourse.shopping.domain.model.Product
+import woowacourse.shopping.domain.model.ProductCount
+import woowacourse.shopping.domain.model.RecentProduct
+import woowacourse.shopping.domain.model.RecentProducts
+import woowacourse.shopping.domain.model.page.LoadMore
+import woowacourse.shopping.domain.model.page.Page
+import woowacourse.shopping.domain.repository.CartRepository
+import woowacourse.shopping.domain.repository.ProductRepository
+import woowacourse.shopping.domain.repository.RecentProductRepository
import woowacourse.shopping.mapper.toDomain
import woowacourse.shopping.mapper.toUi
import woowacourse.shopping.model.UiProduct
+import woowacourse.shopping.model.UiProductCount
import woowacourse.shopping.model.UiRecentProduct
import woowacourse.shopping.ui.shopping.ShoppingContract.Presenter
import woowacourse.shopping.ui.shopping.ShoppingContract.View
class ShoppingPresenter(
view: View,
- private val productRepository: DomainProductRepository,
- private val recentProductRepository: DomainRecentProductRepository,
+ private val productRepository: ProductRepository,
+ private val recentProductRepository: RecentProductRepository,
+ private val cartRepository: CartRepository,
+ private val recentProductSize: Int = 10,
+ private var recentProducts: RecentProducts = RecentProducts(),
+ cartSize: Int = 20,
) : Presenter(view) {
- private var products = Products()
- private var recentProducts = RecentProducts()
-
+ private var cart = Cart()
+ private var currentPage: Page = LoadMore(sizePerPage = cartSize)
+ private val cartProductCount: UiProductCount
+ get() = UiProductCount(cartRepository.getProductInCartSize())
override fun fetchAll() {
- fetchProducts()
+ loadMoreProducts()
fetchRecentProducts()
}
- override fun fetchProducts() {
- val newProducts = productRepository
- .getPartially(TOTAL_LOAD_PRODUCT_SIZE_AT_ONCE, products.lastId)
- products = products.addAll(newProducts)
+ private fun convertToCartProduct(product: Product): DomainCartProduct {
+ val cartEntity = cartRepository.getCartEntity(product.id)
+ return DomainCartProduct(
+ cartEntity.id,
+ product,
+ ProductCount(cartEntity.count),
+ cartEntity.checked
+ )
+ }
- view.updateProducts(products.getItemsByUnit().map { it.toUi() })
- view.updateLoadMoreVisible()
+ override fun fetchRecentProducts() {
+ updateRecentProducts(recentProductRepository.getPartially(recentProductSize))
}
- private fun View.updateLoadMoreVisible() {
- if (products.canLoadMore()) {
- showLoadMoreButton()
- } else {
- hideLoadMoreButton()
- }
+ override fun loadMoreProducts() {
+ updateCart(cart + loadCartProducts(currentPage))
+ view.updateLoadMoreVisible()
+ currentPage = currentPage.next()
}
override fun inquiryProductDetail(product: UiProduct) {
val recentProduct = RecentProduct(product = product.toDomain())
- recentProducts += recentProduct
+ view.navigateToProductDetail(product, recentProducts.getLatest()?.toUi())
+ recentProductRepository.add(recentProduct)
+ updateRecentProducts(recentProducts + recentProduct)
+ }
- view.updateRecentProducts(recentProducts.getItems().map { it.toUi() })
- view.showProductDetail(product)
+ override fun inquiryRecentProductDetail(recentProduct: UiRecentProduct) {
+ view.navigateToProductDetail(recentProduct.product, recentProducts.getLatest()?.toUi())
+ recentProductRepository.add(recentProduct.toDomain())
+ }
- recentProductRepository.add(recentProduct)
+ override fun navigateToCart() {
+ view.navigateToCart()
}
- override fun fetchRecentProducts() {
- recentProducts = RecentProducts(recentProductRepository.getPartially(RECENT_PRODUCT_SIZE))
- view.updateRecentProducts(recentProducts.getItems().map { it.toUi() })
+ override fun increaseCartCount(product: UiProduct, count: Int) {
+ val newProduct = product.toDomain()
+ cartRepository.increaseCartCount(newProduct, count)
+ updateCart(cart.increaseProductCount(newProduct, count))
}
- override fun inquiryRecentProductDetail(recentProduct: UiRecentProduct) {
- view.showProductDetail(recentProduct.product)
- recentProductRepository.add(recentProduct.toDomain())
+ override fun decreaseCartCount(product: UiProduct, count: Int) {
+ val removingProduct = product.toDomain()
+ cartRepository.decreaseCartCount(removingProduct, count)
+ updateCart(cart.decreaseProductCount(removingProduct, count))
+ }
+
+ private fun View.updateLoadMoreVisible() {
+ if (currentPage.hasNext(cart)) showLoadMoreButton() else hideLoadMoreButton()
}
- override fun openBasket() {
- view.navigateToBasketScreen()
+ private fun updateCart(newCart: Cart) {
+ cart = cart.update(newCart)
+ updateCartView()
}
- companion object {
- private const val RECENT_PRODUCT_SIZE = 10
- private const val LOAD_PRODUCT_SIZE_AT_ONCE = 20
- private const val PRODUCT_SIZE_FOR_HAS_NEXT = 1
- private const val TOTAL_LOAD_PRODUCT_SIZE_AT_ONCE =
- LOAD_PRODUCT_SIZE_AT_ONCE + PRODUCT_SIZE_FOR_HAS_NEXT
+ private fun updateCartView() {
+ view.updateCartBadge(cartProductCount)
+ view.updateProducts(currentPage.takeItems(cart).toUi())
}
+
+ private fun updateRecentProducts(newRecentProducts: RecentProducts) {
+ recentProducts = recentProducts.update(newRecentProducts)
+ view.updateRecentProducts(recentProducts.getItems().toUi())
+ }
+
+ private fun loadCartProducts(page: Page): List = productRepository
+ .getProductByPage(page)
+ .map { convertToCartProduct(it) }
}
diff --git a/app/src/main/java/woowacourse/shopping/ui/shopping/recyclerview/adapter/product/ProductAdapter.kt b/app/src/main/java/woowacourse/shopping/ui/shopping/recyclerview/adapter/product/ProductAdapter.kt
index 3cdaeeec4..eb516475c 100644
--- a/app/src/main/java/woowacourse/shopping/ui/shopping/recyclerview/adapter/product/ProductAdapter.kt
+++ b/app/src/main/java/woowacourse/shopping/ui/shopping/recyclerview/adapter/product/ProductAdapter.kt
@@ -1,30 +1,28 @@
package woowacourse.shopping.ui.shopping.recyclerview.adapter.product
import android.view.ViewGroup
-import androidx.recyclerview.widget.DiffUtil
import androidx.recyclerview.widget.ListAdapter
-import woowacourse.shopping.model.UiProduct
+import woowacourse.shopping.model.UiCartProduct
import woowacourse.shopping.ui.shopping.ShoppingViewType
+import woowacourse.shopping.util.diffutil.ProductDiffUtil
+import woowacourse.shopping.util.listener.ProductClickListener
+import woowacourse.shopping.widget.ProductCounterView.OnClickListener
-class ProductAdapter(private val onItemClick: (UiProduct) -> Unit) :
- ListAdapter(productDiffUtil) {
+class ProductAdapter(
+ private val productClickListener: ProductClickListener,
+ private val counterClickListener: OnClickListener,
+) : ListAdapter(ProductDiffUtil) {
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): ProductViewHolder =
- ProductViewHolder(parent) { pos -> onItemClick(currentList[pos]) }
+ ProductViewHolder(
+ parent = parent,
+ productClickListener = productClickListener,
+ counterClickListener = counterClickListener,
+ )
override fun onBindViewHolder(holder: ProductViewHolder, position: Int) {
holder.bind(getItem(position))
}
override fun getItemViewType(position: Int): Int = ShoppingViewType.PRODUCT.value
-
- companion object {
- private val productDiffUtil = object : DiffUtil.ItemCallback() {
- override fun areItemsTheSame(oldItem: UiProduct, newItem: UiProduct): Boolean =
- oldItem.id == newItem.id
-
- override fun areContentsTheSame(oldItem: UiProduct, newItem: UiProduct): Boolean =
- oldItem == newItem
- }
- }
}
diff --git a/app/src/main/java/woowacourse/shopping/ui/shopping/recyclerview/adapter/product/ProductViewHolder.kt b/app/src/main/java/woowacourse/shopping/ui/shopping/recyclerview/adapter/product/ProductViewHolder.kt
index adb5fb809..f1e972b2c 100644
--- a/app/src/main/java/woowacourse/shopping/ui/shopping/recyclerview/adapter/product/ProductViewHolder.kt
+++ b/app/src/main/java/woowacourse/shopping/ui/shopping/recyclerview/adapter/product/ProductViewHolder.kt
@@ -5,20 +5,25 @@ import android.view.ViewGroup
import androidx.recyclerview.widget.RecyclerView
import woowacourse.shopping.R
import woowacourse.shopping.databinding.ItemProductBinding
-import woowacourse.shopping.model.UiProduct
-import woowacourse.shopping.util.extension.setOnSingleClickListener
+import woowacourse.shopping.model.UiCartProduct
+import woowacourse.shopping.util.listener.ProductClickListener
+import woowacourse.shopping.widget.ProductCounterView.OnClickListener
-class ProductViewHolder(parent: ViewGroup, onItemClick: (Int) -> Unit) :
- RecyclerView.ViewHolder(
- LayoutInflater.from(parent.context).inflate(R.layout.item_product, parent, false)
- ) {
+class ProductViewHolder(
+ parent: ViewGroup,
+ productClickListener: ProductClickListener,
+ counterClickListener: OnClickListener,
+) : RecyclerView.ViewHolder(
+ LayoutInflater.from(parent.context).inflate(R.layout.item_product, parent, false)
+) {
private val binding = ItemProductBinding.bind(itemView)
init {
- binding.root.setOnSingleClickListener { onItemClick(bindingAdapterPosition) }
+ binding.productClickListener = productClickListener
+ binding.counterClickListener = counterClickListener
}
- fun bind(product: UiProduct) {
- binding.product = product
+ fun bind(cartProduct: UiCartProduct) {
+ binding.cartProduct = cartProduct
}
}
diff --git a/app/src/main/java/woowacourse/shopping/ui/shopping/recyclerview/adapter/recentproduct/RecentProductAdapter.kt b/app/src/main/java/woowacourse/shopping/ui/shopping/recyclerview/adapter/recentproduct/RecentProductAdapter.kt
index 478ab69fb..bf82dddb5 100644
--- a/app/src/main/java/woowacourse/shopping/ui/shopping/recyclerview/adapter/recentproduct/RecentProductAdapter.kt
+++ b/app/src/main/java/woowacourse/shopping/ui/shopping/recyclerview/adapter/recentproduct/RecentProductAdapter.kt
@@ -1,12 +1,12 @@
package woowacourse.shopping.ui.shopping.recyclerview.adapter.recentproduct
import android.view.ViewGroup
-import androidx.recyclerview.widget.DiffUtil
import androidx.recyclerview.widget.ListAdapter
import woowacourse.shopping.model.UiRecentProduct
+import woowacourse.shopping.util.diffutil.RecentProductDiffUtil
class RecentProductAdapter(private val onItemClick: (UiRecentProduct) -> Unit) :
- ListAdapter(recentProductDiffUtil) {
+ ListAdapter(RecentProductDiffUtil) {
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): RecentProductViewHolder =
RecentProductViewHolder(parent) { pos -> onItemClick(currentList[pos]) }
@@ -14,14 +14,4 @@ class RecentProductAdapter(private val onItemClick: (UiRecentProduct) -> Unit) :
override fun onBindViewHolder(holder: RecentProductViewHolder, position: Int) {
holder.bind(getItem(position))
}
-
- companion object {
- private val recentProductDiffUtil = object : DiffUtil.ItemCallback() {
- override fun areItemsTheSame(oldItem: UiRecentProduct, newItem: UiRecentProduct):
- Boolean = oldItem.id == newItem.id
-
- override fun areContentsTheSame(oldItem: UiRecentProduct, newItem: UiRecentProduct):
- Boolean = oldItem == newItem
- }
- }
}
diff --git a/app/src/main/java/woowacourse/shopping/ui/shopping/recyclerview/layoutmanager/ShoppingGridLayoutManager.kt b/app/src/main/java/woowacourse/shopping/ui/shopping/recyclerview/layoutmanager/ShoppingGridLayoutManager.kt
index f6eb7c95f..fc03e95f4 100644
--- a/app/src/main/java/woowacourse/shopping/ui/shopping/recyclerview/layoutmanager/ShoppingGridLayoutManager.kt
+++ b/app/src/main/java/woowacourse/shopping/ui/shopping/recyclerview/layoutmanager/ShoppingGridLayoutManager.kt
@@ -10,8 +10,8 @@ import woowacourse.shopping.ui.shopping.ShoppingViewType
class ShoppingGridLayoutManager(
context: Context,
adapter: Adapter,
- spanSize: Int = DEFAULT_MAXIMUM_SPAN_SIZE,
-) : GridLayoutManager(context, spanSize) {
+ maxSpanSize: Int = DEFAULT_MAXIMUM_SPAN_SIZE,
+) : GridLayoutManager(context, maxSpanSize) {
init {
spanSizeLookup = object : SpanSizeLookup() {
diff --git a/app/src/main/java/woowacourse/shopping/ui/shopping/recyclerview/listener/EndScrollListener.kt b/app/src/main/java/woowacourse/shopping/ui/shopping/recyclerview/listener/EndScrollListener.kt
deleted file mode 100644
index f1b8ff9b4..000000000
--- a/app/src/main/java/woowacourse/shopping/ui/shopping/recyclerview/listener/EndScrollListener.kt
+++ /dev/null
@@ -1,20 +0,0 @@
-package woowacourse.shopping.ui.shopping.recyclerview.listener
-
-import androidx.recyclerview.widget.RecyclerView
-
-class EndScrollListener(
- private val onEndScroll: () -> Unit,
-) : RecyclerView.OnScrollListener() {
-
- override fun onScrolled(recyclerView: RecyclerView, dx: Int, dy: Int) {
- super.onScrolled(recyclerView, dx, dy)
- if (!recyclerView.canScrollVertically(DIRECTION_SCROLL_DOWN)) {
- onEndScroll()
- }
- }
-
- companion object {
- private const val DIRECTION_SCROLL_DOWN = 1
- }
-
-}
diff --git a/app/src/main/java/woowacourse/shopping/util/ConcatConfig.kt b/app/src/main/java/woowacourse/shopping/util/ConcatConfig.kt
deleted file mode 100644
index 51fb51fd8..000000000
--- a/app/src/main/java/woowacourse/shopping/util/ConcatConfig.kt
+++ /dev/null
@@ -1,8 +0,0 @@
-package woowacourse.shopping.util
-
-import androidx.recyclerview.widget.ConcatAdapter.Config
-
-val isolatedViewTypeConfig: Config
- get() = Config.Builder()
- .setIsolateViewTypes(false)
- .build()
diff --git a/app/src/main/java/woowacourse/shopping/util/bindingadapter/ProductContaierViewBindingAdapter.kt b/app/src/main/java/woowacourse/shopping/util/bindingadapter/ProductContaierViewBindingAdapter.kt
new file mode 100644
index 000000000..fcde3ddd8
--- /dev/null
+++ b/app/src/main/java/woowacourse/shopping/util/bindingadapter/ProductContaierViewBindingAdapter.kt
@@ -0,0 +1,19 @@
+package woowacourse.shopping.util.bindingadapter
+
+import androidx.databinding.BindingAdapter
+import woowacourse.shopping.widget.ProductCounterView
+
+@BindingAdapter("bind:count")
+fun ProductCounterView.setCount(count: Int) {
+ this.count = count
+}
+
+@BindingAdapter("bind:onPlusClick")
+fun ProductCounterView.setOnPlusClick(onClick: Runnable) {
+ setOnPlusClickListener { _, _ -> onClick.run() }
+}
+
+@BindingAdapter("bind:onMinusClick")
+fun ProductCounterView.setOnMinusClick(onClick: Runnable) {
+ setOnMinusClickListener { _, _ -> onClick.run() }
+}
diff --git a/app/src/main/java/woowacourse/shopping/util/bindingadapter/RecyclerViewBindingAdapter.kt b/app/src/main/java/woowacourse/shopping/util/bindingadapter/RecyclerViewBindingAdapter.kt
index 9004c4b4e..a312288d4 100644
--- a/app/src/main/java/woowacourse/shopping/util/bindingadapter/RecyclerViewBindingAdapter.kt
+++ b/app/src/main/java/woowacourse/shopping/util/bindingadapter/RecyclerViewBindingAdapter.kt
@@ -4,16 +4,11 @@ import androidx.databinding.BindingAdapter
import androidx.recyclerview.widget.ConcatAdapter
import androidx.recyclerview.widget.RecyclerView
import androidx.recyclerview.widget.RecyclerView.LayoutManager
-import woowacourse.shopping.ui.shopping.recyclerview.listener.EndScrollListener
-@BindingAdapter("bind:adapter")
-fun RecyclerView.setAdapter(adapter: ConcatAdapter) {
+@BindingAdapter("bind:adapter", "bind:onAdapted", requireAll = false)
+fun RecyclerView.setAdapter(adapter: ConcatAdapter, onAdapted: () -> Unit) {
this.adapter = adapter
-}
-
-@BindingAdapter("bind:onAdapted")
-fun RecyclerView.setOnAdapted(onAdapted: () -> Unit) {
- onAdapted()
+ onAdapted.invoke()
}
@BindingAdapter("bind:fixedSize")
@@ -21,16 +16,12 @@ fun RecyclerView.setFixedSize(fixedSize: Boolean) {
setHasFixedSize(fixedSize)
}
-@BindingAdapter("bind:onEndScroll")
-fun RecyclerView.setOnEndScroll(onEndScroll: () -> Unit) {
- addOnScrollListener(EndScrollListener(onEndScroll))
-}
-
@BindingAdapter("bind:layoutManager")
fun RecyclerView.setLayoutManager(layoutManager: LayoutManager) {
this.layoutManager = layoutManager
}
-interface OnAdaptedListener {
- fun onAdapted()
+@BindingAdapter("bind:animator")
+fun RecyclerView.setAnimator(itemAnimator: RecyclerView.ItemAnimator?) {
+ this.itemAnimator = itemAnimator
}
diff --git a/app/src/main/java/woowacourse/shopping/util/bindingadapter/TextViewBindingAdapter.kt b/app/src/main/java/woowacourse/shopping/util/bindingadapter/TextViewBindingAdapter.kt
new file mode 100644
index 000000000..d1cc2e7ef
--- /dev/null
+++ b/app/src/main/java/woowacourse/shopping/util/bindingadapter/TextViewBindingAdapter.kt
@@ -0,0 +1,11 @@
+package woowacourse.shopping.util.bindingadapter
+
+import android.widget.TextView
+import androidx.databinding.BindingAdapter
+import woowacourse.shopping.R
+import woowacourse.shopping.model.UiPrice
+
+@BindingAdapter("bind:price")
+fun TextView.setPrice(price: UiPrice?) {
+ text = context.getString(R.string.price_format, price?.value)
+}
diff --git a/app/src/main/java/woowacourse/shopping/util/builder/ConcatAdapterBuilder.kt b/app/src/main/java/woowacourse/shopping/util/builder/ConcatAdapterBuilder.kt
new file mode 100644
index 000000000..9ee2d73ab
--- /dev/null
+++ b/app/src/main/java/woowacourse/shopping/util/builder/ConcatAdapterBuilder.kt
@@ -0,0 +1,17 @@
+package woowacourse.shopping.util.builder
+
+import androidx.recyclerview.widget.ConcatAdapter
+import androidx.recyclerview.widget.RecyclerView.Adapter
+import androidx.recyclerview.widget.RecyclerView.ViewHolder
+
+fun isolatedViewTypeConcatAdapter(block: ConcatAdapter.() -> Unit): ConcatAdapter =
+ ConcatAdapter(isolatedViewTypeConfig).apply(block)
+
+fun ConcatAdapter.add(adapter: Adapter) {
+ addAdapter(adapter)
+}
+
+val isolatedViewTypeConfig: ConcatAdapter.Config
+ get() = ConcatAdapter.Config.Builder()
+ .setIsolateViewTypes(false)
+ .build()
diff --git a/app/src/main/java/woowacourse/shopping/util/diffutil/CartDiffUtil.kt b/app/src/main/java/woowacourse/shopping/util/diffutil/CartDiffUtil.kt
new file mode 100644
index 000000000..a7e284057
--- /dev/null
+++ b/app/src/main/java/woowacourse/shopping/util/diffutil/CartDiffUtil.kt
@@ -0,0 +1,16 @@
+package woowacourse.shopping.util.diffutil
+
+import androidx.recyclerview.widget.DiffUtil
+import woowacourse.shopping.model.UiCartProduct
+
+object CartDiffUtil : DiffUtil.ItemCallback() {
+ override fun areItemsTheSame(
+ oldItem: UiCartProduct,
+ newItem: UiCartProduct,
+ ): Boolean = oldItem.id == newItem.id
+
+ override fun areContentsTheSame(
+ oldItem: UiCartProduct,
+ newItem: UiCartProduct,
+ ): Boolean = oldItem == newItem
+}
diff --git a/app/src/main/java/woowacourse/shopping/util/diffutil/ProductDiffUtil.kt b/app/src/main/java/woowacourse/shopping/util/diffutil/ProductDiffUtil.kt
new file mode 100644
index 000000000..280e3a905
--- /dev/null
+++ b/app/src/main/java/woowacourse/shopping/util/diffutil/ProductDiffUtil.kt
@@ -0,0 +1,16 @@
+package woowacourse.shopping.util.diffutil
+
+import androidx.recyclerview.widget.DiffUtil
+import woowacourse.shopping.model.CartProduct
+
+object ProductDiffUtil : DiffUtil.ItemCallback() {
+ override fun areItemsTheSame(
+ oldItem: CartProduct,
+ newItem: CartProduct,
+ ): Boolean = oldItem.product.id == newItem.product.id
+
+ override fun areContentsTheSame(
+ oldItem: CartProduct,
+ newItem: CartProduct,
+ ): Boolean = oldItem == newItem
+}
diff --git a/app/src/main/java/woowacourse/shopping/util/diffutil/RecentProductDiffUtil.kt b/app/src/main/java/woowacourse/shopping/util/diffutil/RecentProductDiffUtil.kt
new file mode 100644
index 000000000..27d83c366
--- /dev/null
+++ b/app/src/main/java/woowacourse/shopping/util/diffutil/RecentProductDiffUtil.kt
@@ -0,0 +1,16 @@
+package woowacourse.shopping.util.diffutil
+
+import androidx.recyclerview.widget.DiffUtil
+import woowacourse.shopping.model.UiRecentProduct
+
+object RecentProductDiffUtil : DiffUtil.ItemCallback() {
+ override fun areItemsTheSame(
+ oldItem: UiRecentProduct,
+ newItem: UiRecentProduct,
+ ): Boolean = oldItem.id == newItem.id
+
+ override fun areContentsTheSame(
+ oldItem: UiRecentProduct,
+ newItem: UiRecentProduct,
+ ): Boolean = oldItem == newItem
+}
diff --git a/app/src/main/java/woowacourse/shopping/util/extension/AndroidExtension.kt b/app/src/main/java/woowacourse/shopping/util/extension/AndroidExtension.kt
deleted file mode 100644
index 877dc72f0..000000000
--- a/app/src/main/java/woowacourse/shopping/util/extension/AndroidExtension.kt
+++ /dev/null
@@ -1,42 +0,0 @@
-package woowacourse.shopping.util.extension
-
-import android.content.Intent
-import android.os.Build
-import android.os.Bundle
-import android.os.Parcelable
-
-@Suppress("DEPRECATION")
-inline fun Intent.getParcelableExtraCompat(key: String): T? {
- return if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU) {
- getParcelableExtra(key, T::class.java)
- } else {
- getParcelableExtra(key) as? T
- }
-}
-
-@Suppress("DEPRECATION")
-inline fun Intent.getParcelableArrayListExtraCompat(key: String): ArrayList? {
- return if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU) {
- getParcelableArrayListExtra(key, T::class.java)
- } else {
- getParcelableArrayListExtra(key)
- }
-}
-
-@Suppress("DEPRECATION")
-inline fun Bundle.getParcelableCompat(key: String): T? {
- return if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU) {
- getParcelable(key, T::class.java)
- } else {
- getParcelable(key) as? T
- }
-}
-
-@Suppress("DEPRECATION")
-inline fun Bundle.getParcelableArrayListCompat(key: String): ArrayList? {
- return if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU) {
- getParcelableArrayList(key, T::class.java)
- } else {
- getParcelableArrayList(key)
- }
-}
diff --git a/app/src/main/java/woowacourse/shopping/util/extension/ContextExtension.kt b/app/src/main/java/woowacourse/shopping/util/extension/ContextExtension.kt
new file mode 100644
index 000000000..72ba5db9a
--- /dev/null
+++ b/app/src/main/java/woowacourse/shopping/util/extension/ContextExtension.kt
@@ -0,0 +1,8 @@
+package woowacourse.shopping.util.extension
+
+import android.content.Context
+import android.widget.Toast
+
+fun Context.showToast(message: String) {
+ Toast.makeText(this, message, Toast.LENGTH_SHORT).show()
+}
diff --git a/app/src/main/java/woowacourse/shopping/util/extension/ImageViewExt.kt b/app/src/main/java/woowacourse/shopping/util/extension/ImageViewExtension.kt
similarity index 100%
rename from app/src/main/java/woowacourse/shopping/util/extension/ImageViewExt.kt
rename to app/src/main/java/woowacourse/shopping/util/extension/ImageViewExtension.kt
diff --git a/app/src/main/java/woowacourse/shopping/util/extension/IntentExtension.kt b/app/src/main/java/woowacourse/shopping/util/extension/IntentExtension.kt
new file mode 100644
index 000000000..580437b98
--- /dev/null
+++ b/app/src/main/java/woowacourse/shopping/util/extension/IntentExtension.kt
@@ -0,0 +1,13 @@
+package woowacourse.shopping.util.extension
+
+import android.content.Intent
+import android.os.Build
+import android.os.Parcelable
+
+inline fun Intent.getParcelableExtraCompat(key: String): T? {
+ return if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU) {
+ getParcelableExtra(key, T::class.java)
+ } else {
+ getParcelableExtra(key) as? T
+ }
+}
diff --git a/app/src/main/java/woowacourse/shopping/util/extension/StringExtension.kt b/app/src/main/java/woowacourse/shopping/util/extension/StringExtension.kt
new file mode 100644
index 000000000..d0257611b
--- /dev/null
+++ b/app/src/main/java/woowacourse/shopping/util/extension/StringExtension.kt
@@ -0,0 +1,10 @@
+package woowacourse.shopping.util.extension
+
+fun String.parseQueryString(): Map {
+ val queryStrings = mutableMapOf()
+ substringAfter("?").split("&").forEach {
+ val (key, value) = it.trim().split("=")
+ queryStrings[key] = value
+ }
+ return queryStrings
+}
diff --git a/app/src/main/java/woowacourse/shopping/util/extension/ToolbarExtension.kt b/app/src/main/java/woowacourse/shopping/util/extension/ToolbarExtension.kt
new file mode 100644
index 000000000..f296ac0b3
--- /dev/null
+++ b/app/src/main/java/woowacourse/shopping/util/extension/ToolbarExtension.kt
@@ -0,0 +1,10 @@
+package woowacourse.shopping.util.extension
+
+import android.view.MenuItem
+import android.view.View
+import androidx.annotation.IdRes
+import androidx.appcompat.widget.Toolbar
+
+fun Toolbar.findItem(@IdRes id: Int): MenuItem = menu.findItem(id)
+
+fun Toolbar.findItemActionView(@IdRes id: Int): View? = findItem(id).actionView
diff --git a/app/src/main/java/woowacourse/shopping/util/extension/ViewDataBindingExtension.kt b/app/src/main/java/woowacourse/shopping/util/extension/ViewDataBindingExtension.kt
new file mode 100644
index 000000000..bf2c992df
--- /dev/null
+++ b/app/src/main/java/woowacourse/shopping/util/extension/ViewDataBindingExtension.kt
@@ -0,0 +1,9 @@
+package woowacourse.shopping.util.extension
+
+import android.app.Activity
+import androidx.databinding.ViewDataBinding
+
+fun T.setContentView(activity: Activity): T = run {
+ activity.setContentView(root)
+ this
+}
diff --git a/app/src/main/java/woowacourse/shopping/util/extension/ViewExtension.kt b/app/src/main/java/woowacourse/shopping/util/extension/ViewExtension.kt
index 6b91b0bd7..205cecbee 100644
--- a/app/src/main/java/woowacourse/shopping/util/extension/ViewExtension.kt
+++ b/app/src/main/java/woowacourse/shopping/util/extension/ViewExtension.kt
@@ -1,6 +1,8 @@
package woowacourse.shopping.util.extension
import android.view.View
+import android.widget.TextView
+import androidx.annotation.IdRes
inline fun View.setOnSingleClickListener(
delay: Long = 500L,
@@ -15,3 +17,6 @@ inline fun View.setOnSingleClickListener(
}
}
}
+
+fun View.findTextView(@IdRes id: Int): TextView? = findViewById(id) ?: null
+
diff --git a/app/src/main/java/woowacourse/shopping/util/factory/PresenterFactory.kt b/app/src/main/java/woowacourse/shopping/util/factory/PresenterFactory.kt
deleted file mode 100644
index 2bdd56a29..000000000
--- a/app/src/main/java/woowacourse/shopping/util/factory/PresenterFactory.kt
+++ /dev/null
@@ -1,50 +0,0 @@
-package woowacourse.shopping.util.factory
-
-import android.content.Context
-import woowacourse.shopping.data.database.ShoppingDatabase
-import woowacourse.shopping.data.database.dao.basket.BasketDaoImpl
-import woowacourse.shopping.data.database.dao.product.ProductDaoImpl
-import woowacourse.shopping.data.database.dao.recentproduct.RecentProductDaoImpl
-import woowacourse.shopping.data.datasource.basket.LocalBasketDataSource
-import woowacourse.shopping.data.datasource.product.LocalProductDataSource
-import woowacourse.shopping.data.datasource.recentproduct.LocalRecentProductDataSource
-import woowacourse.shopping.data.repository.BasketRepository
-import woowacourse.shopping.data.repository.ProductRepository
-import woowacourse.shopping.data.repository.RecentProductRepository
-import woowacourse.shopping.model.UiProduct
-import woowacourse.shopping.ui.basket.BasketContract
-import woowacourse.shopping.ui.basket.BasketPresenter
-import woowacourse.shopping.ui.productdetail.ProductDetailContract
-import woowacourse.shopping.ui.productdetail.ProductDetailPresenter
-import woowacourse.shopping.ui.shopping.ShoppingContract
-import woowacourse.shopping.ui.shopping.ShoppingPresenter
-
-fun createShoppingPresenter(
- view: ShoppingContract.View,
- database: ShoppingDatabase,
-): ShoppingContract.Presenter = ShoppingPresenter(
- view,
- ProductRepository(LocalProductDataSource(ProductDaoImpl(database))),
- RecentProductRepository(
- LocalRecentProductDataSource(RecentProductDaoImpl(database))
- )
-)
-
-fun createProductDetailPresenter(
- view: ProductDetailContract.View,
- context: Context,
- product: UiProduct,
-): ProductDetailContract.Presenter = ProductDetailPresenter(
- view = view,
- basketRepository =
- BasketRepository(LocalBasketDataSource(BasketDaoImpl(ShoppingDatabase(context)))),
- product = product
-)
-
-fun createBasketPresenter(
- view: BasketContract.View,
- database: ShoppingDatabase,
-): BasketContract.Presenter = BasketPresenter(
- view,
- BasketRepository(LocalBasketDataSource(BasketDaoImpl(database)))
-)
diff --git a/app/src/main/java/woowacourse/shopping/util/inject/DaoInject.kt b/app/src/main/java/woowacourse/shopping/util/inject/DaoInject.kt
new file mode 100644
index 000000000..56dad881a
--- /dev/null
+++ b/app/src/main/java/woowacourse/shopping/util/inject/DaoInject.kt
@@ -0,0 +1,13 @@
+package woowacourse.shopping.util.inject
+
+import woowacourse.shopping.data.database.ShoppingDatabase
+import woowacourse.shopping.data.database.dao.cart.CartDao
+import woowacourse.shopping.data.database.dao.cart.CartDaoImpl
+import woowacourse.shopping.data.database.dao.recentproduct.RecentProductDao
+import woowacourse.shopping.data.database.dao.recentproduct.RecentProductDaoImpl
+
+fun injectRecentProductDao(database: ShoppingDatabase): RecentProductDao =
+ RecentProductDaoImpl(database)
+
+fun injectCartDao(database: ShoppingDatabase): CartDao =
+ CartDaoImpl(database)
diff --git a/app/src/main/java/woowacourse/shopping/util/inject/DataSourceInject.kt b/app/src/main/java/woowacourse/shopping/util/inject/DataSourceInject.kt
new file mode 100644
index 000000000..1ff8efb6a
--- /dev/null
+++ b/app/src/main/java/woowacourse/shopping/util/inject/DataSourceInject.kt
@@ -0,0 +1,19 @@
+package woowacourse.shopping.util.inject
+
+import woowacourse.shopping.data.database.dao.cart.CartDao
+import woowacourse.shopping.data.database.dao.recentproduct.RecentProductDao
+import woowacourse.shopping.data.datasource.cart.CartDataSource
+import woowacourse.shopping.data.datasource.cart.LocalCartDataSource
+import woowacourse.shopping.data.datasource.product.ProductDataSource
+import woowacourse.shopping.data.datasource.product.RemoteProductDataSource
+import woowacourse.shopping.data.datasource.recentproduct.LocalRecentProductDataSource
+import woowacourse.shopping.data.datasource.recentproduct.RecentProductDataSource
+
+fun inject(): ProductDataSource.Remote =
+ RemoteProductDataSource()
+
+fun inject(dao: RecentProductDao): RecentProductDataSource.Local =
+ LocalRecentProductDataSource(dao)
+
+fun inject(dao: CartDao): CartDataSource.Local =
+ LocalCartDataSource(dao)
diff --git a/app/src/main/java/woowacourse/shopping/util/inject/DatabaseInject.kt b/app/src/main/java/woowacourse/shopping/util/inject/DatabaseInject.kt
new file mode 100644
index 000000000..7ac350e92
--- /dev/null
+++ b/app/src/main/java/woowacourse/shopping/util/inject/DatabaseInject.kt
@@ -0,0 +1,7 @@
+package woowacourse.shopping.util.inject
+
+import android.content.Context
+import woowacourse.shopping.data.database.ShoppingDatabase
+
+fun createShoppingDatabase(context: Context): ShoppingDatabase =
+ ShoppingDatabase(context)
diff --git a/app/src/main/java/woowacourse/shopping/util/inject/PresenterInject.kt b/app/src/main/java/woowacourse/shopping/util/inject/PresenterInject.kt
new file mode 100644
index 000000000..042af7261
--- /dev/null
+++ b/app/src/main/java/woowacourse/shopping/util/inject/PresenterInject.kt
@@ -0,0 +1,46 @@
+package woowacourse.shopping.util.inject
+
+import android.content.Context
+import woowacourse.shopping.model.UiProduct
+import woowacourse.shopping.model.UiRecentProduct
+import woowacourse.shopping.ui.cart.CartContract
+import woowacourse.shopping.ui.cart.CartPresenter
+import woowacourse.shopping.ui.detail.ProductDetailContract
+import woowacourse.shopping.ui.detail.ProductDetailPresenter
+import woowacourse.shopping.ui.shopping.ShoppingContract
+import woowacourse.shopping.ui.shopping.ShoppingPresenter
+
+fun inject(
+ view: ShoppingContract.View,
+ context: Context,
+): ShoppingContract.Presenter {
+ val database = createShoppingDatabase(context)
+ return ShoppingPresenter(
+ view,
+ inject(inject()),
+ inject(inject(injectRecentProductDao(database))),
+ inject(inject(injectCartDao(database))),
+ )
+}
+
+fun inject(
+ view: ProductDetailContract.View,
+ detailProduct: UiProduct,
+ recentProduct: UiRecentProduct?,
+): ProductDetailContract.Presenter = ProductDetailPresenter(
+ view = view,
+ product = detailProduct,
+ recentProduct = recentProduct,
+)
+
+fun inject(
+ view: CartContract.View,
+ context: Context,
+): CartPresenter {
+ val database = createShoppingDatabase(context)
+ return CartPresenter(
+ view,
+ inject(inject()),
+ inject(inject(injectCartDao(database)))
+ )
+}
diff --git a/app/src/main/java/woowacourse/shopping/util/inject/RepositoryInject.kt b/app/src/main/java/woowacourse/shopping/util/inject/RepositoryInject.kt
new file mode 100644
index 000000000..cee9d42ec
--- /dev/null
+++ b/app/src/main/java/woowacourse/shopping/util/inject/RepositoryInject.kt
@@ -0,0 +1,20 @@
+package woowacourse.shopping.util.inject
+
+import woowacourse.shopping.data.datasource.cart.CartDataSource
+import woowacourse.shopping.data.datasource.product.ProductDataSource
+import woowacourse.shopping.data.datasource.recentproduct.RecentProductDataSource
+import woowacourse.shopping.data.repository.CartRepositoryImpl
+import woowacourse.shopping.data.repository.ProductRepositoryImpl
+import woowacourse.shopping.data.repository.RecentProductRepositoryImpl
+import woowacourse.shopping.domain.repository.CartRepository
+import woowacourse.shopping.domain.repository.ProductRepository
+import woowacourse.shopping.domain.repository.RecentProductRepository
+
+fun inject(localDataSource: ProductDataSource.Remote): ProductRepository =
+ ProductRepositoryImpl(localDataSource)
+
+fun inject(localDataSource: RecentProductDataSource.Local): RecentProductRepository =
+ RecentProductRepositoryImpl(localDataSource)
+
+fun inject(localDataSource: CartDataSource.Local): CartRepository =
+ CartRepositoryImpl(localDataSource)
diff --git a/app/src/main/java/woowacourse/shopping/util/listener/ProductClickListener.kt b/app/src/main/java/woowacourse/shopping/util/listener/ProductClickListener.kt
new file mode 100644
index 000000000..c21b9ce5e
--- /dev/null
+++ b/app/src/main/java/woowacourse/shopping/util/listener/ProductClickListener.kt
@@ -0,0 +1,8 @@
+package woowacourse.shopping.util.listener
+
+import woowacourse.shopping.model.Product
+
+interface ProductClickListener {
+ fun onClickProduct(product: Product)
+ fun onClickProductPlus(product: Product)
+}
diff --git a/app/src/main/java/woowacourse/shopping/widget/ProductCounterView.kt b/app/src/main/java/woowacourse/shopping/widget/ProductCounterView.kt
new file mode 100644
index 000000000..ecae142a3
--- /dev/null
+++ b/app/src/main/java/woowacourse/shopping/widget/ProductCounterView.kt
@@ -0,0 +1,82 @@
+package woowacourse.shopping.widget
+
+import android.content.Context
+import android.util.AttributeSet
+import android.view.LayoutInflater
+import androidx.constraintlayout.widget.ConstraintLayout
+import woowacourse.shopping.R
+import woowacourse.shopping.databinding.ViewCounterBinding
+import woowacourse.shopping.model.UiProduct
+import kotlin.properties.Delegates
+
+class ProductCounterView : ConstraintLayout {
+ private val binding by lazy {
+ ViewCounterBinding.inflate(LayoutInflater.from(context), this, true)
+ }
+ var count: Int by Delegates.observable(INITIAL_COUNT) { _, _, newCount ->
+ binding.countTextView.text = newCount.toString()
+ }
+ private var minCount: Int = DEFAULT_MIN_COUNT
+ private var maxCount: Int = DEFAULT_MAX_COUNT
+
+ init {
+ binding.count = count
+ binding.counterPlusButton.setOnClickListener { plusCount() }
+ binding.counterMinusButton.setOnClickListener { minusCount() }
+ }
+
+ constructor(context: Context) : super(context)
+
+ constructor(context: Context, attrs: AttributeSet) : super(context, attrs) {
+ initTypedArrayValue(attrs)
+ }
+
+ constructor(context: Context, attrs: AttributeSet, defStyleAttr: Int) : super(context, attrs, defStyleAttr) {
+ initTypedArrayValue(attrs)
+ }
+
+ constructor(context: Context, attrs: AttributeSet, defStyleAttr: Int, defStyleRes: Int) : super(context, attrs, defStyleAttr, defStyleRes) {
+ initTypedArrayValue(attrs)
+ }
+
+ private fun initTypedArrayValue(attrs: AttributeSet) {
+ context.obtainStyledAttributes(attrs, R.styleable.ProductCounterView).use {
+ count = it.getInt(R.styleable.ProductCounterView_count, INITIAL_COUNT)
+ minCount = it.getInt(R.styleable.ProductCounterView_min_count, DEFAULT_MIN_COUNT)
+ maxCount = it.getInt(R.styleable.ProductCounterView_max_count, DEFAULT_MAX_COUNT)
+ }
+ }
+
+ fun setOnPlusClickListener(onPlusClick: (view: ProductCounterView, newCount: Int) -> Unit) {
+ binding.counterPlusButton.setOnClickListener {
+ plusCount()
+ onPlusClick(this, count)
+ }
+ }
+
+ fun setOnMinusClickListener(onMinusClick: (view: ProductCounterView, newCount: Int) -> Unit) {
+ binding.counterMinusButton.setOnClickListener {
+ minusCount()
+ onMinusClick(this, count)
+ }
+ }
+
+ private fun plusCount() {
+ if (count < maxCount) ++count
+ }
+
+ private fun minusCount() {
+ if (count > minCount) --count
+ }
+
+ companion object {
+ private const val INITIAL_COUNT: Int = 1
+ private const val DEFAULT_MIN_COUNT = 1
+ private const val DEFAULT_MAX_COUNT = 99
+ }
+
+ interface OnClickListener {
+ fun onClickCounterPlus(product: UiProduct)
+ fun onClickCounterMinus(product: UiProduct)
+ }
+}
diff --git a/app/src/main/res/color/color_order_button.xml b/app/src/main/res/color/color_order_button.xml
new file mode 100644
index 000000000..811f8a010
--- /dev/null
+++ b/app/src/main/res/color/color_order_button.xml
@@ -0,0 +1,5 @@
+
+
+
+
+
diff --git a/app/src/main/res/drawable/ic_plus.xml b/app/src/main/res/drawable/ic_plus.xml
new file mode 100644
index 000000000..a9503fd36
--- /dev/null
+++ b/app/src/main/res/drawable/ic_plus.xml
@@ -0,0 +1,10 @@
+
+
+
diff --git a/app/src/main/res/drawable/shape_cart_count_badge.xml b/app/src/main/res/drawable/shape_cart_count_badge.xml
new file mode 100644
index 000000000..c5aaeaa96
--- /dev/null
+++ b/app/src/main/res/drawable/shape_cart_count_badge.xml
@@ -0,0 +1,5 @@
+
+
+
+
diff --git a/app/src/main/res/drawable/shape_counter_minus.xml b/app/src/main/res/drawable/shape_counter_minus.xml
new file mode 100644
index 000000000..937236cee
--- /dev/null
+++ b/app/src/main/res/drawable/shape_counter_minus.xml
@@ -0,0 +1,14 @@
+
+
+
+ -
+
+
+
+
+
+
+
diff --git a/app/src/main/res/drawable/shape_counter_plus.xml b/app/src/main/res/drawable/shape_counter_plus.xml
new file mode 100644
index 000000000..db3b31321
--- /dev/null
+++ b/app/src/main/res/drawable/shape_counter_plus.xml
@@ -0,0 +1,14 @@
+
+
+
+ -
+
+
+
+
+
+
+
diff --git a/app/src/main/res/drawable/shape_woowa_round_4_white_rect.xml b/app/src/main/res/drawable/shape_woowa_round_4_white_rect.xml
new file mode 100644
index 000000000..35f4685a6
--- /dev/null
+++ b/app/src/main/res/drawable/shape_woowa_round_4_white_rect.xml
@@ -0,0 +1,6 @@
+
+
+
+
+
diff --git a/app/src/main/res/layout/activity_basket.xml b/app/src/main/res/layout/activity_basket.xml
deleted file mode 100644
index 248e8116e..000000000
--- a/app/src/main/res/layout/activity_basket.xml
+++ /dev/null
@@ -1,108 +0,0 @@
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
diff --git a/app/src/main/res/layout/activity_cart.xml b/app/src/main/res/layout/activity_cart.xml
new file mode 100644
index 000000000..0784bcfd5
--- /dev/null
+++ b/app/src/main/res/layout/activity_cart.xml
@@ -0,0 +1,182 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/app/src/main/res/layout/activity_product_detail.xml b/app/src/main/res/layout/activity_product_detail.xml
index 532c874ef..28540aace 100644
--- a/app/src/main/res/layout/activity_product_detail.xml
+++ b/app/src/main/res/layout/activity_product_detail.xml
@@ -1,103 +1,179 @@
+
+
+
+
+ name="detailProduct"
+ type="woowacourse.shopping.model.Product" />
+
+
-
+ android:fillViewport="true">
-
-
-
-
-
-
-
+ tools:context=".ui.detail.ProductDetailActivity">
-
-
-
-
-
-
-
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/app/src/main/res/layout/activity_shopping.xml b/app/src/main/res/layout/activity_shopping.xml
index c9768f9ed..294aaf7ed 100644
--- a/app/src/main/res/layout/activity_shopping.xml
+++ b/app/src/main/res/layout/activity_shopping.xml
@@ -26,7 +26,8 @@
android:id="@+id/shopping_tool_bar"
android:layout_width="match_parent"
android:layout_height="?actionBarSize"
- android:background="@color/woowa_toolbar_gray"
+ android:background="@color/woowa_dark_gray"
+ android:paddingEnd="20dp"
app:layout_constraintTop_toTopOf="parent"
app:menu="@menu/menu_shopping"
app:title="@string/tb_shopping"
@@ -37,8 +38,10 @@
android:layout_width="match_parent"
android:layout_height="0dp"
bind:adapter="@{adapter}"
+ bind:animator="@{null}"
bind:fixedSize="@{true}"
bind:layoutManager="@{ShoppingGridLayoutManager.create(context, adapter)}"
+ bind:onAdapted="@{() -> presenter.fetchAll()}"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintTop_toBottomOf="@+id/shopping_tool_bar"
app:spanCount="2"
diff --git a/app/src/main/res/layout/item_basket.xml b/app/src/main/res/layout/item_cart.xml
similarity index 56%
rename from app/src/main/res/layout/item_basket.xml
rename to app/src/main/res/layout/item_cart.xml
index a8636c3e3..11cb095b3 100644
--- a/app/src/main/res/layout/item_basket.xml
+++ b/app/src/main/res/layout/item_cart.xml
@@ -6,9 +6,17 @@
+
+
+
+
+ name="cartClickListener"
+ type="woowacourse.shopping.ui.cart.listener.CartClickListener" />
+
+
@@ -68,12 +90,27 @@
android:id="@+id/product_price_text_view"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
- android:text="@{@string/price_format(product.price.value)}"
+ android:includeFontPadding="false"
+ bind:price="@{cartProduct.product.price}"
android:textColor="@color/woowa_text_black"
android:textSize="16sp"
- app:layout_constraintBottom_toBottomOf="@+id/product_image_view"
+ app:layout_constraintBottom_toTopOf="@+id/counter_view"
app:layout_constraintEnd_toEndOf="@id/close_button"
tools:text="99,800원" />
+
+
diff --git a/app/src/main/res/layout/item_product.xml b/app/src/main/res/layout/item_product.xml
index da8b1050c..ed9b3f401 100644
--- a/app/src/main/res/layout/item_product.xml
+++ b/app/src/main/res/layout/item_product.xml
@@ -6,14 +6,25 @@
+
+
+
+
+ name="productClickListener"
+ type="woowacourse.shopping.util.listener.ProductClickListener" />
+
+
@@ -21,7 +32,7 @@
android:id="@+id/product_image_view"
android:layout_width="match_parent"
android:layout_height="0dp"
- bind:imageUrl="@{product.imageUrl}"
+ bind:imageUrl="@{cartProduct.product.imageUrl}"
android:scaleType="centerCrop"
app:layout_constraintDimensionRatio="1"
app:layout_constraintEnd_toEndOf="parent"
@@ -29,6 +40,40 @@
app:layout_constraintTop_toTopOf="parent"
tools:srcCompat="@tools:sample/avatars" />
+
+
+
+
+
diff --git a/app/src/main/res/layout/layout_cart_badge.xml b/app/src/main/res/layout/layout_cart_badge.xml
new file mode 100644
index 000000000..0609f53b9
--- /dev/null
+++ b/app/src/main/res/layout/layout_cart_badge.xml
@@ -0,0 +1,39 @@
+
+
+
+
+
+
+
+
diff --git a/app/src/main/res/layout/layout_product_counter_dialog.xml b/app/src/main/res/layout/layout_product_counter_dialog.xml
new file mode 100644
index 000000000..1cd54aa46
--- /dev/null
+++ b/app/src/main/res/layout/layout_product_counter_dialog.xml
@@ -0,0 +1,81 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/app/src/main/res/layout/view_counter.xml b/app/src/main/res/layout/view_counter.xml
new file mode 100644
index 000000000..b764b1775
--- /dev/null
+++ b/app/src/main/res/layout/view_counter.xml
@@ -0,0 +1,67 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/app/src/main/res/menu/menu_shopping.xml b/app/src/main/res/menu/menu_shopping.xml
index 6bbb9bc75..14b0ad366 100644
--- a/app/src/main/res/menu/menu_shopping.xml
+++ b/app/src/main/res/menu/menu_shopping.xml
@@ -2,8 +2,8 @@
diff --git a/app/src/main/res/values/attrs.xml b/app/src/main/res/values/attrs.xml
new file mode 100644
index 000000000..5303f77ee
--- /dev/null
+++ b/app/src/main/res/values/attrs.xml
@@ -0,0 +1,8 @@
+
+
+
+
+
+
+
+
diff --git a/app/src/main/res/values/colors.xml b/app/src/main/res/values/colors.xml
index bb26a63ab..9547c8bc3 100644
--- a/app/src/main/res/values/colors.xml
+++ b/app/src/main/res/values/colors.xml
@@ -8,12 +8,12 @@
#FF000000
#FFFFFFFF
- #555555
+ #555555
#333333
#AAAAAA
- #04C09E
+ #04C09E
#EBEBEB
#E6E6E6
#04C09E
-
+ #D5D5D5
diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml
index a2df4c177..d41b34f33 100644
--- a/app/src/main/res/values/strings.xml
+++ b/app/src/main/res/values/strings.xml
@@ -10,7 +10,7 @@
금액
- 장바구니 담기
+ 장바구니 담기
Cart
@@ -19,6 +19,14 @@
]]>
1
화면 닫기
- 장바구니
-
+ 장바구니
+ -
+ +
+ 마지막으로 본 상품
+ 담기
+ 주문하기(%d)
+ 주문하기
+ 전체
+ 제품 %d개를 성공적으로 주문하였습니다!
+ 주문에 실패하였습니다!
diff --git a/app/src/test/java/woowacourse/shopping/basket/BasketPresenterTest.kt b/app/src/test/java/woowacourse/shopping/basket/BasketPresenterTest.kt
deleted file mode 100644
index a0ed4a7cf..000000000
--- a/app/src/test/java/woowacourse/shopping/basket/BasketPresenterTest.kt
+++ /dev/null
@@ -1,108 +0,0 @@
-package woowacourse.shopping.basket
-
-import io.mockk.every
-import io.mockk.mockk
-import io.mockk.slot
-import io.mockk.verify
-import org.junit.Assert.assertEquals
-import org.junit.Before
-import org.junit.Test
-import woowacourse.shopping.domain.PageNumber
-import woowacourse.shopping.domain.repository.BasketRepository
-import woowacourse.shopping.ui.basket.BasketContract
-import woowacourse.shopping.ui.basket.BasketPresenter
-
-internal class BasketPresenterTest {
-
- private lateinit var presenter: BasketContract.Presenter
- private lateinit var view: BasketContract.View
- private lateinit var basketRepository: BasketRepository
-
- @Before
- fun setUp() {
- basketRepository = mockk(relaxed = true)
- view = mockk(relaxed = true)
- presenter = BasketPresenter(view, basketRepository)
- }
-
- @Test
- internal fun 장바구니를_목록을_갱신하면_현재_페이지에_해당하는_아이템을_보여주고_네비게이터를_갱신한다() {
- // given
- /* ... */
-
- // when
- presenter.fetchBasket()
-
- // then
- verify(exactly = 1) { basketRepository.getPartially(any()) }
- verify(exactly = 1) { view.updateBasket(any()) }
- verify(exactly = 1) { view.updateNavigatorEnabled(any(), any()) }
- verify(exactly = 1) { view.updatePageNumber(any()) }
- }
-
- @Test
- internal fun 이전_장바구니를_불러오면_페이지를_변경하고_장바구니를_갱신한다() {
- // given
- val page = 2
- presenter = BasketPresenter(view, basketRepository, currentPage = PageNumber(page))
-
- val currentPage = slot()
- every { basketRepository.getPartially(capture(currentPage)) } returns mockk(relaxed = true)
-
- // when
- presenter.fetchPrevious()
-
- // then
- assertEquals(currentPage.captured, PageNumber(page - 1))
- verify(exactly = 1) { view.updateBasket(any()) }
- verify(exactly = 1) { view.updateNavigatorEnabled(any(), any()) }
- verify(exactly = 1) { view.updatePageNumber(any()) }
- }
-
- @Test
- internal fun 다음_장바구니를_불러오면_페이지를_변경하고_장바구니를_갱신한다() {
- // given
- val page = 1
- presenter = BasketPresenter(view, basketRepository, currentPage = PageNumber(page))
-
- val currentPage = slot()
- every { basketRepository.getPartially(capture(currentPage)) } returns mockk(relaxed = true)
-
- // when
- presenter.fetchNext()
-
- // then
- assertEquals(currentPage.captured, PageNumber(page + 1))
- verify(exactly = 1) { view.updateBasket(any()) }
- verify(exactly = 1) { view.updateNavigatorEnabled(any(), any()) }
- verify(exactly = 1) { view.updatePageNumber(any()) }
- }
-
- @Test
- internal fun 장바구니_목록에_있는_제품을_제거하면_뷰를_갱신한다() {
- // given
- /* ... */
-
- // when
- presenter.removeBasketProduct(mockk(relaxed = true))
-
- // then
- verify(exactly = 1) { basketRepository.remove(any()) }
- verify(exactly = 1) { basketRepository.getPartially(any()) }
- verify(exactly = 1) { view.updateBasket(any()) }
- verify(exactly = 1) { view.updateNavigatorEnabled(any(), any()) }
- verify(exactly = 1) { view.updatePageNumber(any()) }
- }
-
- @Test
- internal fun 종료하면_화면을_닫는다() {
- // given
- /* ... */
-
- // when
- presenter.closeScreen()
-
- // then
- verify(exactly = 1) { view.closeScreen() }
- }
-}
diff --git a/app/src/test/java/woowacourse/shopping/ui/cart/CartPresenterTest.kt b/app/src/test/java/woowacourse/shopping/ui/cart/CartPresenterTest.kt
new file mode 100644
index 000000000..5247d494f
--- /dev/null
+++ b/app/src/test/java/woowacourse/shopping/ui/cart/CartPresenterTest.kt
@@ -0,0 +1,114 @@
+package woowacourse.shopping.ui.cart
+
+import io.mockk.every
+import io.mockk.mockk
+import io.mockk.slot
+import io.mockk.verify
+import org.junit.Assert.assertEquals
+import org.junit.Before
+import org.junit.Test
+import woowacourse.shopping.domain.model.page.Page
+import woowacourse.shopping.domain.model.page.Pagination
+import woowacourse.shopping.domain.repository.CartRepository
+import woowacourse.shopping.domain.repository.ProductRepository
+import woowacourse.shopping.mapper.toDomain
+import woowacourse.shopping.model.Product
+import woowacourse.shopping.model.UiPrice
+
+internal class CartPresenterTest {
+ private lateinit var presenter: CartContract.Presenter
+ private lateinit var view: CartContract.View
+ private lateinit var productRepository: ProductRepository
+ private lateinit var cartRepository: CartRepository
+
+ @Before
+ fun setUp() {
+ cartRepository = mockk(relaxed = true)
+ productRepository = mockk(relaxed = true)
+ view = mockk(relaxed = true)
+ presenter = CartPresenter(view, productRepository, cartRepository)
+ }
+
+ @Test
+ internal fun 장바구니를_목록을_갱신하면_현재_페이지에_해당하는_아이템을_보여주고_네비게이터를_갱신한다() {
+ // given
+ val page = 1
+
+ // when
+ presenter.fetchCart(page)
+
+ // then
+ verify(exactly = 1) { view.updateCart(any()) }
+ verify(exactly = 1) { view.updateNavigatorEnabled(any(), any()) }
+ verify(exactly = 1) { view.updatePageNumber(any()) }
+ }
+
+ @Test
+ internal fun 이전_장바구니를_불러오면_페이지를_변경하고_장바구니를_갱신한다() {
+ // given
+ val page = 2
+ presenter = CartPresenter(view, productRepository, cartRepository)
+
+ val currentPage = slot()
+
+ // when
+ presenter.fetchCart(page - 1)
+
+ // then
+ assertEquals(currentPage.captured, Pagination(page - 1))
+ verify(exactly = 1) { view.updateCart(any()) }
+ verify(exactly = 1) { view.updateNavigatorEnabled(any(), any()) }
+ verify(exactly = 1) { view.updatePageNumber(any()) }
+ }
+
+ @Test
+ internal fun 다음_장바구니를_불러오면_페이지를_변경하고_장바구니를_갱신한다() {
+ // given
+ val page = 1
+ presenter = CartPresenter(view, productRepository, cartRepository)
+
+ val currentPage = slot()
+
+ // when
+ presenter.fetchCart(page + 1)
+
+ // then
+ assertEquals(currentPage.captured, Pagination(page + 1))
+ verify(exactly = 1) { view.updateCart(any()) }
+ verify(exactly = 1) { view.updateNavigatorEnabled(any(), any()) }
+ verify(exactly = 1) { view.updatePageNumber(any()) }
+ }
+
+ @Test
+ internal fun 장바구니_목록에_있는_제품을_제거하면_뷰를_갱신한다() {
+ // given
+ val products = MutableList(8) { id ->
+ Product(id, "상품 $id", UiPrice(1000), "")
+ }
+ val product = Product(0, "상품 0", UiPrice(1000), "")
+ every { cartRepository.decreaseCartCount(product.toDomain(), 1) } answers {
+ products.remove(product)
+ }
+
+ // when
+ presenter.removeProduct(product)
+
+ // then
+ verify(exactly = 1) { cartRepository.decreaseCartCount(product.toDomain(), 1) }
+ verify(exactly = 1) { view.updateCart(any()) }
+ verify(exactly = 1) { view.updateNavigatorEnabled(any(), any()) }
+ verify(exactly = 1) { view.updatePageNumber(any()) }
+ }
+
+ @Test
+ internal fun 종료하면_화면을_닫는다() {
+ // given
+ /* ... */
+
+ // when
+ presenter.navigateToHome()
+
+ // then
+ verify(exactly = 1) { view.navigateToHome() }
+ }
+}
diff --git a/app/src/test/java/woowacourse/shopping/ui/detail/ProductDetailPresenterTest.kt b/app/src/test/java/woowacourse/shopping/ui/detail/ProductDetailPresenterTest.kt
new file mode 100644
index 000000000..817871502
--- /dev/null
+++ b/app/src/test/java/woowacourse/shopping/ui/detail/ProductDetailPresenterTest.kt
@@ -0,0 +1,48 @@
+package woowacourse.shopping.ui.detail
+
+import io.mockk.mockk
+import io.mockk.verify
+import org.junit.Before
+import org.junit.Test
+import woowacourse.shopping.model.UiProduct
+import woowacourse.shopping.model.UiRecentProduct
+
+internal class ProductDetailPresenterTest {
+ private lateinit var presenter: ProductDetailContract.Presenter
+ private lateinit var view: ProductDetailContract.View
+ private lateinit var detailProduct: UiProduct
+ private lateinit var recentProduct: UiRecentProduct
+
+ @Before
+ fun setUp() {
+ view = mockk(relaxed = true)
+ detailProduct = mockk(relaxed = true)
+ recentProduct = mockk(relaxed = true)
+ presenter = ProductDetailPresenter(view, detailProduct, recentProduct)
+ }
+
+ @Test
+ internal fun 프레젠터가_초기화될_때_상품_정보를_보여준다() {
+ // given
+ /* ... */
+
+ // when
+ /* init */
+
+ // then
+ verify(exactly = 1) { view.showProductDetail(detailProduct) }
+ verify(exactly = 1) { view.showLastViewedProductDetail(recentProduct.product) }
+ }
+
+ @Test
+ internal fun 장바구니에_상품을_추가한다() {
+ // given
+ /* ... */
+
+ // when
+ presenter.inquiryProductCounter()
+
+ // then
+ verify(exactly = 1) { view.showProductCounter(detailProduct) }
+ }
+}
diff --git a/app/src/test/java/woowacourse/shopping/ui/productdetail/ProductDetailPresenterTest.kt b/app/src/test/java/woowacourse/shopping/ui/productdetail/ProductDetailPresenterTest.kt
deleted file mode 100644
index 12714b4ad..000000000
--- a/app/src/test/java/woowacourse/shopping/ui/productdetail/ProductDetailPresenterTest.kt
+++ /dev/null
@@ -1,47 +0,0 @@
-package woowacourse.shopping.ui.productdetail
-
-import io.mockk.mockk
-import io.mockk.verify
-import org.junit.Before
-import org.junit.Test
-import woowacourse.shopping.domain.repository.BasketRepository
-
-internal class ProductDetailPresenterTest {
- private lateinit var presenter: ProductDetailContract.Presenter
- private lateinit var view: ProductDetailContract.View
- private lateinit var basketRepository: BasketRepository
-
- @Before
- fun setUp() {
- view = mockk(relaxed = true)
- basketRepository = mockk(relaxed = true)
- presenter = ProductDetailPresenter(view, basketRepository, mockk(relaxed = true))
- }
-
- @Test
- internal fun 프레젠터가_초기화될_때_상품_정보를_보여준다() {
- // given
- /* ... */
-
- // when
- /* init */
-
- // then
- verify(exactly = 1) { view.showProductImage(any()) }
- verify(exactly = 1) { view.showProductName(any()) }
- verify(exactly = 1) { view.showProductPrice(any()) }
- }
-
- @Test
- internal fun 장바구니에_상품을_추가한다() {
- // given
- /* ... */
-
- // when
- presenter.addBasketProduct()
-
- // then
- verify(exactly = 1) { basketRepository.add(any()) }
- verify(exactly = 1) { view.navigateToBasketScreen() }
- }
-}
diff --git a/app/src/test/java/woowacourse/shopping/ui/shopping/ShoppingPresenterTest.kt b/app/src/test/java/woowacourse/shopping/ui/shopping/ShoppingPresenterTest.kt
index d1dbbb9f1..94d8e9d27 100644
--- a/app/src/test/java/woowacourse/shopping/ui/shopping/ShoppingPresenterTest.kt
+++ b/app/src/test/java/woowacourse/shopping/ui/shopping/ShoppingPresenterTest.kt
@@ -5,37 +5,51 @@ import io.mockk.mockk
import io.mockk.verify
import org.junit.Before
import org.junit.Test
-import woowacourse.shopping.domain.repository.DomainProductRepository
-import woowacourse.shopping.domain.repository.DomainRecentProductRepository
+import woowacourse.shopping.domain.model.Price
+import woowacourse.shopping.domain.model.Product
+import woowacourse.shopping.domain.model.RecentProduct
+import woowacourse.shopping.domain.model.RecentProducts
+import woowacourse.shopping.domain.repository.CartRepository
+import woowacourse.shopping.domain.repository.ProductRepository
+import woowacourse.shopping.domain.repository.RecentProductRepository
+import woowacourse.shopping.mapper.toDomain
+import woowacourse.shopping.mapper.toUi
+import woowacourse.shopping.model.UiPrice
import woowacourse.shopping.model.UiProduct
import woowacourse.shopping.model.UiRecentProduct
internal class ShoppingPresenterTest {
-
private lateinit var presenter: ShoppingContract.Presenter
private lateinit var view: ShoppingContract.View
- private lateinit var productRepository: DomainProductRepository
- private lateinit var recentProductRepository: DomainRecentProductRepository
+ private lateinit var productRepository: ProductRepository
+ private lateinit var recentProductRepository: RecentProductRepository
+ private lateinit var cartRepository: CartRepository
@Before
fun setUp() {
view = mockk(relaxed = true)
productRepository = mockk(relaxed = true)
recentProductRepository = mockk(relaxed = true)
- presenter = ShoppingPresenter(view, productRepository, recentProductRepository)
+ cartRepository = mockk(relaxed = true)
+ presenter =
+ ShoppingPresenter(view, productRepository, recentProductRepository, cartRepository)
}
@Test
- internal fun 패치_올을_호출하면_제품과_최근_본_목록을_갱신한다() {
+ internal fun fetchAll_메서드를_호출하면_제품과_최근_본_목록을_갱신한다() {
// given
- every { productRepository.getPartially(any(), any()) } answers { listOf() }
+ every { recentProductRepository.getPartially(any()) } returns RecentProducts(
+ items = listOf(
+ RecentProduct(1, Product(1, "상품", Price(1000), "상품 이미지"))
+ )
+ )
// when
presenter.fetchAll()
// then
- verify(exactly = 1) { view.updateProducts(any()) }
verify(exactly = 1) { view.hideLoadMoreButton() }
+ verify(exactly = 1) { view.updateRecentProducts(any()) }
}
@Test
@@ -48,20 +62,29 @@ internal class ShoppingPresenterTest {
// then
verify(exactly = 1) { view.updateRecentProducts(any()) }
- verify(exactly = 1) { view.showProductDetail(any()) }
+ verify(exactly = 1) { view.navigateToProductDetail(product, any()) }
verify(exactly = 1) { recentProductRepository.add(any()) }
}
@Test
internal fun 최근_제품_목록을_갱신한다() {
// given
- /* ... */
+ val recentProducts = mockk(relaxed = true)
+ presenter = ShoppingPresenter(
+ view,
+ productRepository,
+ recentProductRepository,
+ cartRepository,
+ 10,
+ recentProducts,
+ )
// when
presenter.fetchRecentProducts()
// then
- verify(exactly = 1) { view.updateRecentProducts(any()) }
+ val expected = recentProducts.getItems().toUi()
+ verify(exactly = 1) { view.updateRecentProducts(expected) }
}
@Test
@@ -73,7 +96,7 @@ internal class ShoppingPresenterTest {
presenter.inquiryRecentProductDetail(recentProduct)
// then
- verify(exactly = 1) { view.showProductDetail(any()) }
+ verify(exactly = 1) { view.navigateToProductDetail(any(), any()) }
verify(exactly = 1) { recentProductRepository.add(any()) }
}
@@ -83,9 +106,36 @@ internal class ShoppingPresenterTest {
/* ... */
// when
- presenter.openBasket()
+ presenter.navigateToCart()
// then
- verify(exactly = 1) { view.navigateToBasketScreen() }
+ verify(exactly = 1) { view.navigateToCart() }
+ }
+
+ @Test
+ internal fun 장바구니_화면으로_이동한다() {
+ // given
+ /* ... */
+
+ // when
+ presenter.navigateToCart()
+
+ // then
+ verify(exactly = 1) { view.navigateToCart() }
+ }
+
+ @Test
+ internal fun `제품_개수를_증가시킨다`() {
+ // given
+ val product = UiProduct(0, "제품", UiPrice(1000), "")
+ val count = 3
+
+ // when
+ presenter.increaseCartCount(product, count)
+
+ // then
+ verify(exactly = 1) { cartRepository.increaseCartCount(product.toDomain(), count) }
+ verify(exactly = 1) { view.updateCartBadge(any()) }
+ verify(exactly = 1) { view.updateProducts(any()) }
}
}
diff --git a/domain/src/main/java/woowacourse/shopping/domain/Basket.kt b/domain/src/main/java/woowacourse/shopping/domain/Basket.kt
deleted file mode 100644
index 0d9a8f679..000000000
--- a/domain/src/main/java/woowacourse/shopping/domain/Basket.kt
+++ /dev/null
@@ -1,9 +0,0 @@
-package woowacourse.shopping.domain
-
-data class Basket(private val products: List) {
- fun add(product: Product): Basket =
- Basket(products + product)
-
- fun delete(product: Product): Basket =
- Basket(products - product)
-}
diff --git a/domain/src/main/java/woowacourse/shopping/domain/BasketProduct.kt b/domain/src/main/java/woowacourse/shopping/domain/BasketProduct.kt
deleted file mode 100644
index 7c4f97f5d..000000000
--- a/domain/src/main/java/woowacourse/shopping/domain/BasketProduct.kt
+++ /dev/null
@@ -1,6 +0,0 @@
-package woowacourse.shopping.domain
-
-data class BasketProduct(
- val id: Int,
- val product: Product,
-)
diff --git a/domain/src/main/java/woowacourse/shopping/domain/PageNumber.kt b/domain/src/main/java/woowacourse/shopping/domain/PageNumber.kt
deleted file mode 100644
index 8abb57d53..000000000
--- a/domain/src/main/java/woowacourse/shopping/domain/PageNumber.kt
+++ /dev/null
@@ -1,29 +0,0 @@
-package woowacourse.shopping.domain
-
-typealias DomainPageNumber = PageNumber
-
-data class PageNumber(
- val value: Int = DEFAULT_PAGE,
- val sizePerPage: Int = DEFAULT_SIZE_PER_PAGE,
-) {
- init {
- require(value >= DEFAULT_PAGE) { INVALID_PAGE_NUMBER_ERROR_MESSAGE }
- }
-
- fun hasPrevious(): Boolean = value > MIN_PAGE
-
- operator fun inc(): PageNumber =
- copy(value = value + 1)
-
- operator fun dec(): PageNumber =
- copy(value = (value - 1).coerceAtLeast(MIN_PAGE))
-
- companion object {
- private const val DEFAULT_PAGE = 1
- private const val DEFAULT_SIZE_PER_PAGE = 5
- private const val MIN_PAGE = 1
-
- private const val INVALID_PAGE_NUMBER_ERROR_MESSAGE =
- "페이지 번호는 1 이상의 정수만 가능합니다."
- }
-}
diff --git a/domain/src/main/java/woowacourse/shopping/domain/Products.kt b/domain/src/main/java/woowacourse/shopping/domain/Products.kt
deleted file mode 100644
index 780e9d437..000000000
--- a/domain/src/main/java/woowacourse/shopping/domain/Products.kt
+++ /dev/null
@@ -1,30 +0,0 @@
-package woowacourse.shopping.domain
-
-typealias DomainProducts = Products
-
-data class Products(
- private val items: List = emptyList(),
- private val loadUnit: Int = DEFAULT_LOAD_AT_ONCE,
-) {
- val lastId: Int
- get() = items.maxOfOrNull { it.id } ?: -1
-
- fun addAll(newItems: List): Products = copy(items = items + newItems)
-
- fun add(newItem: Product): Products = copy(items = items + newItem)
-
- fun canLoadMore(): Boolean =
- items.size >= loadUnit && (items.size % loadUnit >= 1 || loadUnit == 1 && items.size > loadUnit)
-
- fun getItems(): List = items.toList()
-
- fun getItemsByUnit(): List = items.take(
- (items.size / loadUnit).coerceAtLeast(1) * loadUnit
- )
-
- operator fun plus(item: Product): Products = add(item)
-
- companion object {
- private const val DEFAULT_LOAD_AT_ONCE = 20
- }
-}
diff --git a/domain/src/main/java/woowacourse/shopping/domain/model/Cart.kt b/domain/src/main/java/woowacourse/shopping/domain/model/Cart.kt
new file mode 100644
index 000000000..55cc2423c
--- /dev/null
+++ b/domain/src/main/java/woowacourse/shopping/domain/model/Cart.kt
@@ -0,0 +1,62 @@
+package woowacourse.shopping.domain.model
+
+import woowacourse.shopping.domain.model.page.Page
+
+typealias DomainCart = Cart
+
+data class Cart(
+ val items: List = emptyList(),
+ val minProductSize: Int = 0,
+) {
+ fun increaseProductCount(product: Product, count: Int = 1): Cart =
+ copy(items = items
+ .map { item -> if (item.product.id == product.id) item.plusCount(count) else item }
+ .distinctBy { it.product.id })
+
+ fun decreaseProductCount(product: Product, count: Int = 1): Cart =
+ copy(items = items
+ .map { item -> if (item.canDecreaseCount(product)) item.minusCount(count) else item }
+ .filter { it.selectedCount.value >= minProductSize }
+ .distinctBy { it.product.id })
+
+ private fun CartProduct.canDecreaseCount(product: Product): Boolean =
+ this.product.id == product.id && selectedCount.value > minProductSize
+
+ fun select(product: Product): Cart =
+ copy(items = items.map { item ->
+ if (item.product.id == product.id) item.select() else item
+ })
+
+ fun unselect(product: Product): Cart =
+ copy(items = items.map { item ->
+ if (item.product.id == product.id) item.unselect() else item
+ })
+
+ fun selectAll(page: Page): Cart {
+ val cartProductsOfPage = page.takeItems(this)
+ return copy(items = items.map { item ->
+ cartProductsOfPage.find { it.id == item.id }?.select() ?: item
+ })
+ }
+
+ fun unselectAll(page: Page): Cart {
+ val cartProductsOfPage = page.takeItems(this)
+ return copy(items = items.map { item ->
+ cartProductsOfPage.find { it.id == item.id }?.unselect() ?: item
+ })
+ }
+
+ fun update(cart: Cart): Cart =
+ copy(items = cart.items.distinctBy { it.product.id })
+
+ fun update(cartProducts: List): Cart =
+ copy(items = cartProducts.distinctBy { it.product.id })
+
+ fun getCheckedProductTotalPrice(): Int = items.sumOf { it.getTotalPrice(true) }
+
+ operator fun plus(items: Cart): Cart =
+ copy(items = (this.items + items.items).distinctBy { it.product.id })
+
+ operator fun plus(items: List): Cart =
+ copy(items = (this.items + items).distinctBy { it.product.id })
+}
diff --git a/domain/src/main/java/woowacourse/shopping/domain/model/CartEntity.kt b/domain/src/main/java/woowacourse/shopping/domain/model/CartEntity.kt
new file mode 100644
index 000000000..79bbe6cce
--- /dev/null
+++ b/domain/src/main/java/woowacourse/shopping/domain/model/CartEntity.kt
@@ -0,0 +1,10 @@
+package woowacourse.shopping.domain.model
+
+typealias DomainCartEntity = CartEntity
+
+class CartEntity(
+ val id: Int,
+ val productId: Int,
+ val count: Int,
+ val checked: Boolean,
+)
diff --git a/domain/src/main/java/woowacourse/shopping/domain/model/CartProduct.kt b/domain/src/main/java/woowacourse/shopping/domain/model/CartProduct.kt
new file mode 100644
index 000000000..cf3d79fdb
--- /dev/null
+++ b/domain/src/main/java/woowacourse/shopping/domain/model/CartProduct.kt
@@ -0,0 +1,31 @@
+package woowacourse.shopping.domain.model
+
+typealias DomainCartProduct = CartProduct
+
+data class CartProduct(
+ val id: Int = 0,
+ val product: Product,
+ val selectedCount: ProductCount = ProductCount(0),
+ val isChecked: Boolean,
+) {
+ val productId: Int = product.id
+
+ fun plusCount(count: Int = 1): CartProduct =
+ copy(selectedCount = selectedCount + count)
+
+ fun minusCount(count: Int = 1): CartProduct =
+ copy(selectedCount = selectedCount - count)
+
+ fun select(): CartProduct =
+ copy(isChecked = true)
+
+ fun unselect(): CartProduct =
+ copy(isChecked = false)
+
+ fun getTotalPrice(onlyChecked: Boolean): Int {
+ if (onlyChecked && isChecked) {
+ return product.price.value * selectedCount.value
+ }
+ return 0
+ }
+}
diff --git a/domain/src/main/java/woowacourse/shopping/domain/Price.kt b/domain/src/main/java/woowacourse/shopping/domain/model/Price.kt
similarity index 87%
rename from domain/src/main/java/woowacourse/shopping/domain/Price.kt
rename to domain/src/main/java/woowacourse/shopping/domain/model/Price.kt
index 63f2d2e66..59293a1db 100644
--- a/domain/src/main/java/woowacourse/shopping/domain/Price.kt
+++ b/domain/src/main/java/woowacourse/shopping/domain/model/Price.kt
@@ -1,4 +1,4 @@
-package woowacourse.shopping.domain
+package woowacourse.shopping.domain.model
data class Price(val value: Int) {
init {
diff --git a/domain/src/main/java/woowacourse/shopping/domain/Product.kt b/domain/src/main/java/woowacourse/shopping/domain/model/Product.kt
similarity index 72%
rename from domain/src/main/java/woowacourse/shopping/domain/Product.kt
rename to domain/src/main/java/woowacourse/shopping/domain/model/Product.kt
index 12b9ab783..f06d41f5f 100644
--- a/domain/src/main/java/woowacourse/shopping/domain/Product.kt
+++ b/domain/src/main/java/woowacourse/shopping/domain/model/Product.kt
@@ -1,4 +1,4 @@
-package woowacourse.shopping.domain
+package woowacourse.shopping.domain.model
data class Product(
val id: Int,
diff --git a/domain/src/main/java/woowacourse/shopping/domain/model/ProductCount.kt b/domain/src/main/java/woowacourse/shopping/domain/model/ProductCount.kt
new file mode 100644
index 000000000..8e6fb2882
--- /dev/null
+++ b/domain/src/main/java/woowacourse/shopping/domain/model/ProductCount.kt
@@ -0,0 +1,21 @@
+package woowacourse.shopping.domain.model
+
+data class ProductCount(
+ val value: Int,
+ private val minCount: Int = EMPTY_COUNT,
+) {
+ operator fun plus(count: Int): ProductCount = copy(value = value + count)
+
+ operator fun minus(count: Int): ProductCount =
+ copy(value = (value - count).coerceAtLeast(minCount))
+
+ operator fun plus(count: ProductCount): ProductCount =
+ copy(value = value + count.value)
+
+ operator fun minus(count: ProductCount): ProductCount =
+ copy(value = (value - count.value).coerceAtLeast(minCount))
+
+ companion object {
+ private const val EMPTY_COUNT = 0
+ }
+}
diff --git a/domain/src/main/java/woowacourse/shopping/domain/RecentProduct.kt b/domain/src/main/java/woowacourse/shopping/domain/model/RecentProduct.kt
similarity index 64%
rename from domain/src/main/java/woowacourse/shopping/domain/RecentProduct.kt
rename to domain/src/main/java/woowacourse/shopping/domain/model/RecentProduct.kt
index 772df4e09..cb5e544b6 100644
--- a/domain/src/main/java/woowacourse/shopping/domain/RecentProduct.kt
+++ b/domain/src/main/java/woowacourse/shopping/domain/model/RecentProduct.kt
@@ -1,4 +1,4 @@
-package woowacourse.shopping.domain
+package woowacourse.shopping.domain.model
data class RecentProduct(
val id: Int = 0,
diff --git a/domain/src/main/java/woowacourse/shopping/domain/RecentProducts.kt b/domain/src/main/java/woowacourse/shopping/domain/model/RecentProducts.kt
similarity index 54%
rename from domain/src/main/java/woowacourse/shopping/domain/RecentProducts.kt
rename to domain/src/main/java/woowacourse/shopping/domain/model/RecentProducts.kt
index 133ccc7c8..9bb6d3dea 100644
--- a/domain/src/main/java/woowacourse/shopping/domain/RecentProducts.kt
+++ b/domain/src/main/java/woowacourse/shopping/domain/model/RecentProducts.kt
@@ -1,10 +1,9 @@
-package woowacourse.shopping.domain
+package woowacourse.shopping.domain.model
-class RecentProducts(
- _items: List = emptyList(),
+data class RecentProducts(
+ private val items: List = emptyList(),
private val maxCount: Int = 10,
) {
- private val items: List = _items.take(maxCount)
fun add(newItem: RecentProduct): RecentProducts {
val newItems = items.toMutableList()
@@ -14,7 +13,11 @@ class RecentProducts(
return RecentProducts(newItems.take(maxCount), maxCount)
}
+ fun update(newItems: RecentProducts): RecentProducts = copy(items = newItems.items)
+
+ fun getLatest(): RecentProduct? = items.firstOrNull()
+
operator fun plus(newItem: RecentProduct): RecentProducts = add(newItem)
- fun getItems(): List = items.map { it }.toList()
+ fun getItems(): List = items.take(maxCount).map { it.copy() }
}
diff --git a/domain/src/main/java/woowacourse/shopping/domain/model/page/LoadMore.kt b/domain/src/main/java/woowacourse/shopping/domain/model/page/LoadMore.kt
new file mode 100644
index 000000000..bf83b918f
--- /dev/null
+++ b/domain/src/main/java/woowacourse/shopping/domain/model/page/LoadMore.kt
@@ -0,0 +1,29 @@
+package woowacourse.shopping.domain.model.page
+
+import woowacourse.shopping.domain.model.Cart
+import woowacourse.shopping.domain.model.CartProduct
+import woowacourse.shopping.domain.util.safeSubList
+
+typealias DomainLoadMore = LoadMore
+
+class LoadMore(
+ value: Int = 1,
+ sizePerPage: Int = 20,
+) : Page(value, sizePerPage) {
+ override fun getStartPage(): Page = LoadMore(1, sizePerPage)
+
+ override fun hasPrevious(): Boolean = value > 1
+
+ override fun hasNext(cart: Cart): Boolean = cart.items.size >= value * sizePerPage
+
+ override fun next(): Page = LoadMore(value + 1, sizePerPage)
+
+ override fun update(value: Int): Page = LoadMore(value, sizePerPage)
+
+ override fun takeItems(cart: Cart): List =
+ cart.items.take(value * sizePerPage)
+
+ override fun getCheckedProductSize(cart: Cart): Int = cart.items
+ .safeSubList(0, value * sizePerPage)
+ .count { item -> item.isChecked }
+}
diff --git a/domain/src/main/java/woowacourse/shopping/domain/model/page/Page.kt b/domain/src/main/java/woowacourse/shopping/domain/model/page/Page.kt
new file mode 100644
index 000000000..66933b546
--- /dev/null
+++ b/domain/src/main/java/woowacourse/shopping/domain/model/page/Page.kt
@@ -0,0 +1,38 @@
+package woowacourse.shopping.domain.model.page
+
+import woowacourse.shopping.domain.model.Cart
+import woowacourse.shopping.domain.model.CartProduct
+
+typealias DomainPage = Page
+
+abstract class Page(
+ val value: Int = DEFAULT_PAGE,
+ val sizePerPage: Int = DEFAULT_SIZE_PER_PAGE,
+) {
+ init {
+ require(value >= MIN_PAGE) { INVALID_PAGE_NUMBER_ERROR_MESSAGE }
+ }
+
+ abstract fun getStartPage(): Page
+
+ abstract fun hasPrevious(): Boolean
+
+ abstract fun hasNext(cart: Cart): Boolean
+
+ abstract fun next(): Page
+
+ abstract fun update(value: Int): Page
+
+ abstract fun takeItems(cart: Cart): List
+
+ abstract fun getCheckedProductSize(cart: Cart): Int
+
+ companion object {
+ private const val DEFAULT_PAGE = 1
+ private const val DEFAULT_SIZE_PER_PAGE = 5
+
+ private const val MIN_PAGE = 1
+ private const val INVALID_PAGE_NUMBER_ERROR_MESSAGE =
+ "페이지 번호는 1 이상의 정수만 가능합니다."
+ }
+}
diff --git a/domain/src/main/java/woowacourse/shopping/domain/model/page/Pagination.kt b/domain/src/main/java/woowacourse/shopping/domain/model/page/Pagination.kt
new file mode 100644
index 000000000..5472479c4
--- /dev/null
+++ b/domain/src/main/java/woowacourse/shopping/domain/model/page/Pagination.kt
@@ -0,0 +1,33 @@
+package woowacourse.shopping.domain.model.page
+
+import woowacourse.shopping.domain.model.Cart
+import woowacourse.shopping.domain.model.CartProduct
+import woowacourse.shopping.domain.util.safeSubList
+
+typealias DomainPagination = Pagination
+
+class Pagination(
+ value: Int = 1,
+ sizePerPage: Int = 5,
+) : Page(value, sizePerPage) {
+ override fun getStartPage(): Page = Pagination(FIRST_PAGE, sizePerPage)
+
+ override fun hasPrevious(): Boolean = value > FIRST_PAGE
+
+ override fun hasNext(cart: Cart): Boolean = cart.items.size > sizePerPage * value
+
+ override fun next(): Page = Pagination(value + 1, sizePerPage)
+
+ override fun update(value: Int): Page = Pagination(value, sizePerPage)
+
+ override fun takeItems(cart: Cart): List =
+ cart.items.safeSubList((value - 1) * sizePerPage, value * sizePerPage)
+
+ override fun getCheckedProductSize(cart: Cart): Int =
+ takeItems(cart).count { item -> item.isChecked }
+
+
+ companion object {
+ private const val FIRST_PAGE = 1
+ }
+}
diff --git a/domain/src/main/java/woowacourse/shopping/domain/repository/BasketRepository.kt b/domain/src/main/java/woowacourse/shopping/domain/repository/BasketRepository.kt
deleted file mode 100644
index bb12793fe..000000000
--- a/domain/src/main/java/woowacourse/shopping/domain/repository/BasketRepository.kt
+++ /dev/null
@@ -1,12 +0,0 @@
-package woowacourse.shopping.domain.repository
-
-import woowacourse.shopping.domain.PageNumber
-import woowacourse.shopping.domain.Product
-
-typealias DomainBasketRepository = BasketRepository
-
-interface BasketRepository {
- fun getPartially(page: PageNumber): List
- fun add(product: Product)
- fun remove(product: Product)
-}
diff --git a/domain/src/main/java/woowacourse/shopping/domain/repository/CartRepository.kt b/domain/src/main/java/woowacourse/shopping/domain/repository/CartRepository.kt
new file mode 100644
index 000000000..823447cd7
--- /dev/null
+++ b/domain/src/main/java/woowacourse/shopping/domain/repository/CartRepository.kt
@@ -0,0 +1,17 @@
+package woowacourse.shopping.domain.repository
+
+import woowacourse.shopping.domain.model.CartEntity
+import woowacourse.shopping.domain.model.CartProduct
+import woowacourse.shopping.domain.model.Product
+
+interface CartRepository {
+ fun getAllCartEntities(): List
+ fun getCartEntity(productId: Int): CartEntity
+ fun increaseCartCount(product: Product, count: Int)
+ fun decreaseCartCount(product: Product, count: Int)
+ fun deleteByProductId(productId: Int)
+ fun getProductInCartSize(): Int
+ fun update(cartProducts: List)
+ fun getCheckedProductCount(): Int
+ fun removeCheckedProducts()
+}
diff --git a/domain/src/main/java/woowacourse/shopping/domain/repository/ProductRepository.kt b/domain/src/main/java/woowacourse/shopping/domain/repository/ProductRepository.kt
index 58b73b7a0..fa276857f 100644
--- a/domain/src/main/java/woowacourse/shopping/domain/repository/ProductRepository.kt
+++ b/domain/src/main/java/woowacourse/shopping/domain/repository/ProductRepository.kt
@@ -1,9 +1,9 @@
package woowacourse.shopping.domain.repository
-import woowacourse.shopping.domain.Product
-
-typealias DomainProductRepository = ProductRepository
+import woowacourse.shopping.domain.model.Product
+import woowacourse.shopping.domain.model.page.Page
interface ProductRepository {
- fun getPartially(size: Int, startId: Int): List
+ fun getProductByPage(page: Page): List
+ fun findProductById(id: Int): Product?
}
diff --git a/domain/src/main/java/woowacourse/shopping/domain/repository/RecentProductRepository.kt b/domain/src/main/java/woowacourse/shopping/domain/repository/RecentProductRepository.kt
index 1f82fd0ed..6796f02bc 100644
--- a/domain/src/main/java/woowacourse/shopping/domain/repository/RecentProductRepository.kt
+++ b/domain/src/main/java/woowacourse/shopping/domain/repository/RecentProductRepository.kt
@@ -1,10 +1,9 @@
package woowacourse.shopping.domain.repository
-import woowacourse.shopping.domain.RecentProduct
-
-typealias DomainRecentProductRepository = RecentProductRepository
+import woowacourse.shopping.domain.model.RecentProduct
+import woowacourse.shopping.domain.model.RecentProducts
interface RecentProductRepository {
fun add(recentProduct: RecentProduct)
- fun getPartially(size: Int): List
+ fun getPartially(size: Int): RecentProducts
}
diff --git a/domain/src/main/java/woowacourse/shopping/domain/util/ListExtension.kt b/domain/src/main/java/woowacourse/shopping/domain/util/ListExtension.kt
new file mode 100644
index 000000000..a08e3f66d
--- /dev/null
+++ b/domain/src/main/java/woowacourse/shopping/domain/util/ListExtension.kt
@@ -0,0 +1,9 @@
+package woowacourse.shopping.domain.util
+
+fun List.safeSubList(startIndex: Int, endIndex: Int): List =
+ if (startIndex < size) {
+ val safeEndIndex = if (endIndex < size) endIndex else size
+ subList(startIndex, safeEndIndex)
+ } else {
+ emptyList()
+ }
diff --git a/domain/src/test/java/woowacourse/shopping/domain/BasketTest.kt b/domain/src/test/java/woowacourse/shopping/domain/BasketTest.kt
deleted file mode 100644
index dd5bf648d..000000000
--- a/domain/src/test/java/woowacourse/shopping/domain/BasketTest.kt
+++ /dev/null
@@ -1,31 +0,0 @@
-package woowacourse.shopping.domain
-
-import org.assertj.core.api.Assertions.assertThat
-import org.junit.jupiter.api.Test
-
-class BasketTest {
- @Test
- fun `장바구니에 상품을 담는다`() {
- val products = listOf()
- val basket = Basket(products)
- val product = Product(0, "새상품", Price(1000), "")
-
- val actual = basket.add(product)
- val expected = Basket(products + product)
-
- assertThat(actual).isEqualTo(expected)
- }
-
- @Test
- fun `장바구니에 상품을 삭제한다`() {
- val products =
- listOf(Product(0, "새상품", Price(1000), ""), Product(1, "새상품", Price(1000), ""))
- val basket = Basket(products)
- val product = Product(0, "새상품", Price(1000), "")
-
- val actual = basket.delete(product)
- val expected = Basket(products - product)
-
- assertThat(actual).isEqualTo(expected)
- }
-}
diff --git a/domain/src/test/java/woowacourse/shopping/domain/PageNumberTest.kt b/domain/src/test/java/woowacourse/shopping/domain/PageNumberTest.kt
deleted file mode 100644
index 47be9faf1..000000000
--- a/domain/src/test/java/woowacourse/shopping/domain/PageNumberTest.kt
+++ /dev/null
@@ -1,82 +0,0 @@
-package woowacourse.shopping.domain
-
-import org.assertj.core.api.Assertions.assertThat
-import org.junit.jupiter.api.Test
-import org.junit.jupiter.api.assertThrows
-import org.junit.jupiter.params.ParameterizedTest
-import org.junit.jupiter.params.provider.ValueSource
-
-internal class PageNumberTest {
-
- @ParameterizedTest
- @ValueSource(ints = [0, -1, -2, -3, -4, -5])
- internal fun `페이지 번호가 1보다 작으면 예외가 발생한다`(number: Int) {
- assertThrows { PageNumber(number) }
- }
-
- @ParameterizedTest
- @ValueSource(ints = [2, 3, 4, 5, 6, 100])
- internal fun `페이지 번호가 1보다 크면 이전 페이지가 존재한다`(number: Int) {
- // given
- val page = PageNumber(number)
-
- // when
- val actual = page.hasPrevious()
-
- // then
- assertThat(actual).isTrue
- }
-
- @Test
- internal fun `페이지 번호가 1이면 이전 페이지가 존재하지 않는다`() {
- // given
- val page = PageNumber(1)
-
- // when
- val actual = page.hasPrevious()
-
- // then
- assertThat(actual).isFalse
- }
-
- @Test
- internal fun `페이지 번호가 1이면, 감소 시켰을 때 더 이상 감소하지 않는다`() {
- // given
- val expected = PageNumber(1)
- var page = PageNumber(1)
-
- // when
- val actual = --page
-
- // then
- assertThat(actual).isEqualTo(expected)
- }
-
- @ParameterizedTest
- @ValueSource(ints = [2, 3, 4, 5, 6, 100])
- internal fun `페이지 번호가 1보다 크면, 감소 시켰을 때 1만큼 감소한다`(currentNumber: Int) {
- // given
- var page = PageNumber(currentNumber)
- val expected = PageNumber(currentNumber - 1)
-
- // when
- val actual = --page
-
- // then
- assertThat(actual).isEqualTo(expected)
- }
-
- @ParameterizedTest
- @ValueSource(ints = [2, 3, 4, 5, 6, 100])
- internal fun `페이지 번호를 증가시켰을 때, 1만큼 증가한다`(currentNumber: Int) {
- // given
- var page = PageNumber(currentNumber)
- val expected = PageNumber(currentNumber + 1)
-
- // when
- val actual = ++page
-
- // then
- assertThat(actual).isEqualTo(expected)
- }
-}
diff --git a/domain/src/test/java/woowacourse/shopping/domain/ProductsTest.kt b/domain/src/test/java/woowacourse/shopping/domain/ProductsTest.kt
deleted file mode 100644
index bcf739351..000000000
--- a/domain/src/test/java/woowacourse/shopping/domain/ProductsTest.kt
+++ /dev/null
@@ -1,50 +0,0 @@
-package woowacourse.shopping.domain
-
-import org.assertj.core.api.Assertions.assertThat
-import org.junit.jupiter.params.ParameterizedTest
-import org.junit.jupiter.params.provider.CsvSource
-import org.junit.jupiter.params.provider.ValueSource
-
-internal class ProductsTest {
- @ParameterizedTest
- @ValueSource(ints = [1, 10, 100])
- internal fun `마지막 아이디를_반환한다`(count: Int) {
- // given
- val items = List(count) { id -> Product(id + 1, "상품 $id", Price(1000), "") }
- val products = Products(items)
-
- // when
- val actual = products.lastId
-
- // then
- assertThat(actual).isEqualTo(count)
- }
-
- @ParameterizedTest
- @CsvSource("2, 1", "15, 10", "101, 100")
- internal fun `아이템_개수가_로딩_단위_보다_많으면_데이터를_더_불러올_수_있는_상태이다`(itemCount: Int, loadUnit: Int) {
- // given
- val items = List(itemCount) { id -> Product(id + 1, "상품 $id", Price(1000), "") }
- val products = Products(items, loadUnit)
-
- // when
- val actual = products.canLoadMore()
-
- // then
- assertThat(actual).isTrue
- }
-
- @ParameterizedTest
- @CsvSource("5, 2, 4", "15, 10, 10", "101, 20, 100")
- internal fun `아이템을_로딩_단위별로_가져온다`(itemCount: Int, loadUnit: Int, expected: Int) {
- // given
- val items = List(itemCount) { id -> Product(id + 1, "상품 $id", Price(1000), "") }
- val products = Products(items, loadUnit)
-
- // when
- val actual = products.getItemsByUnit().size
-
- // then
- assertThat(actual).isEqualTo(expected)
- }
-}
diff --git a/domain/src/test/java/woowacourse/shopping/domain/fixture/CartFixture.kt b/domain/src/test/java/woowacourse/shopping/domain/fixture/CartFixture.kt
new file mode 100644
index 000000000..e7955cfb8
--- /dev/null
+++ b/domain/src/test/java/woowacourse/shopping/domain/fixture/CartFixture.kt
@@ -0,0 +1,40 @@
+package woowacourse.shopping.domain.fixture
+
+import woowacourse.shopping.domain.model.Cart
+import woowacourse.shopping.domain.model.CartProduct
+import woowacourse.shopping.domain.model.Price
+import woowacourse.shopping.domain.model.Product
+import woowacourse.shopping.domain.model.ProductCount
+
+internal val ALL_UNCHECKED_CARTS: Cart = Cart(
+ items = List(100) { id ->
+ CartProduct(
+ id = id,
+ Product(id = id, name = "상품$id", price = Price(1000), imageUrl = ""),
+ selectedCount = ProductCount(1),
+ isChecked = false
+ )
+ }
+)
+
+internal val ALL_CHECKED_CARTS: Cart = Cart(
+ items = List(100) { id ->
+ CartProduct(
+ id = id,
+ Product(id = id, name = "상품$id", price = Price(1000), imageUrl = ""),
+ selectedCount = ProductCount(1),
+ isChecked = true
+ )
+ }
+)
+
+internal fun getCart(size: Int): Cart = Cart(
+ items = List(size) { id ->
+ CartProduct(
+ id = id,
+ Product(id = id, name = "상품$id", price = Price(1000), imageUrl = ""),
+ selectedCount = ProductCount(1),
+ isChecked = false
+ )
+ }
+)
diff --git a/domain/src/test/java/woowacourse/shopping/domain/model/CartProductTest.kt b/domain/src/test/java/woowacourse/shopping/domain/model/CartProductTest.kt
new file mode 100644
index 000000000..ee4878e58
--- /dev/null
+++ b/domain/src/test/java/woowacourse/shopping/domain/model/CartProductTest.kt
@@ -0,0 +1,123 @@
+package woowacourse.shopping.domain.model
+
+import org.assertj.core.api.Assertions.assertThat
+import org.junit.jupiter.api.Test
+
+class CartProductTest {
+ @Test
+ internal fun `선택된 제품 개수를 증가시킨다`() {
+ // given
+ val product = Product(1, "상품", Price(1000), "")
+ val cartProduct = CartProduct(product = product, isChecked = false)
+ val expected =
+ CartProduct(product = product, selectedCount = ProductCount(2), isChecked = false)
+
+ // when
+ val actual = cartProduct.plusCount(2)
+
+ // then
+ assertThat(actual).isEqualTo(expected)
+ }
+
+ @Test
+ internal fun `선택된 제품 개수를 감소시킨다`() {
+ // given
+ val product = Product(1, "상품", Price(1000), "")
+ val cartProduct = CartProduct(
+ product = product,
+ selectedCount = ProductCount(3),
+ isChecked = false
+ )
+ val expected = CartProduct(
+ product = product,
+ selectedCount = ProductCount(1),
+ isChecked = false
+ )
+
+ // when
+ val actual = cartProduct.minusCount(2)
+
+ // then
+ assertThat(actual).isEqualTo(expected)
+ }
+
+ @Test
+ internal fun `제품을 선택한다`() {
+ // given
+ val product = Product(1, "상품", Price(1000), "")
+ val cartProduct = CartProduct(
+ product = product,
+ selectedCount = ProductCount(2),
+ isChecked = false
+ )
+ val expected = CartProduct(
+ product = product,
+ selectedCount = ProductCount(2),
+ isChecked = true
+ )
+
+ // when
+ val actual = cartProduct.select()
+
+ // then
+ assertThat(actual).isEqualTo(expected)
+ }
+
+ @Test
+ internal fun `제품을 선택을 해제한다`() {
+ // given
+ val product = Product(1, "상품", Price(1000), "")
+ val cartProduct = CartProduct(
+ product = product,
+ selectedCount = ProductCount(2),
+ isChecked = true
+ )
+ val expected = CartProduct(
+ product = product,
+ selectedCount = ProductCount(2),
+ isChecked = false
+ )
+
+ // when
+ val actual = cartProduct.unselect()
+
+ // then
+ assertThat(actual).isEqualTo(expected)
+ }
+
+ @Test
+ internal fun `체크된 제품의 개수와 가격을 곱하여 총 합을 반환한다`() {
+ // given
+ val product = Product(1, "상품", Price(1000), "")
+ val cartProduct = CartProduct(
+ product = product,
+ selectedCount = ProductCount(2),
+ isChecked = true
+ )
+ val expected = 2000
+
+ // when
+ val actual = cartProduct.getTotalPrice(onlyChecked = true)
+
+ // then
+ assertThat(actual).isEqualTo(expected)
+ }
+
+ @Test
+ internal fun `체크되지 않은 제품은 총 가격에 포함하지 않는다`() {
+ // given
+ val product = Product(1, "상품", Price(1000), "")
+ val cartProduct = CartProduct(
+ product = product,
+ selectedCount = ProductCount(2),
+ isChecked = false
+ )
+ val expected = 0
+
+ // when
+ val actual = cartProduct.getTotalPrice(onlyChecked = true)
+
+ // then
+ assertThat(actual).isEqualTo(expected)
+ }
+}
diff --git a/domain/src/test/java/woowacourse/shopping/domain/model/CartTest.kt b/domain/src/test/java/woowacourse/shopping/domain/model/CartTest.kt
new file mode 100644
index 000000000..1da133a22
--- /dev/null
+++ b/domain/src/test/java/woowacourse/shopping/domain/model/CartTest.kt
@@ -0,0 +1,34 @@
+package woowacourse.shopping.domain.model
+
+import org.assertj.core.api.Assertions.assertThat
+import org.junit.jupiter.api.Test
+import woowacourse.shopping.domain.model.Cart
+import woowacourse.shopping.domain.model.Price
+import woowacourse.shopping.domain.model.Product
+
+class CartTest {
+// @Test
+// fun `장바구니에 상품을 담는다`() {
+// val products = listOf()
+// val cart = Cart(products)
+// val product = Product(0, "새상품", Price(1000), "")
+//
+// val actual = cart.increaseProductCount(product)
+// val expected = Cart(products + product)
+//
+// assertThat(actual).isEqualTo(expected)
+// }
+//
+// @Test
+// fun `장바구니에 상품을 삭제한다`() {
+// val products =
+// listOf(Product(0, "새상품", Price(1000), ""), Product(1, "새상품", Price(1000), ""))
+// val cart = Cart(products)
+// val product = Product(0, "새상품", Price(1000), "")
+//
+// val actual = cart.delete(product)
+// val expected = Cart(products - product)
+//
+// assertThat(actual).isEqualTo(expected)
+// }
+}
diff --git a/domain/src/test/java/woowacourse/shopping/domain/model/LoadMoreTest.kt b/domain/src/test/java/woowacourse/shopping/domain/model/LoadMoreTest.kt
new file mode 100644
index 000000000..dc05d4802
--- /dev/null
+++ b/domain/src/test/java/woowacourse/shopping/domain/model/LoadMoreTest.kt
@@ -0,0 +1,94 @@
+package woowacourse.shopping.domain.model
+
+import org.assertj.core.api.Assertions.assertThat
+import org.junit.jupiter.api.Test
+import org.junit.jupiter.api.assertThrows
+import org.junit.jupiter.params.ParameterizedTest
+import org.junit.jupiter.params.provider.ValueSource
+import woowacourse.shopping.domain.fixture.ALL_CHECKED_CARTS
+import woowacourse.shopping.domain.fixture.ALL_UNCHECKED_CARTS
+import woowacourse.shopping.domain.model.page.LoadMore
+
+internal class LoadMoreTest {
+ @Test
+ internal fun `첫 번째 페이지를 반환한다`() {
+ // given
+ val page = LoadMore(3)
+
+ // when
+ val actual = page.getStartPage()
+
+ // then
+ assertThat(actual).usingRecursiveComparison().isEqualTo(LoadMore(1))
+ }
+
+ @ParameterizedTest
+ @ValueSource(ints = [0, -1, -2, -3, -4, -5])
+ internal fun `페이지 번호가 1보다 작으면 예외가 발생한다`(number: Int) {
+ assertThrows { LoadMore(number) }
+ }
+
+ @ParameterizedTest
+ @ValueSource(ints = [2, 3, 4, 5, 6, 100])
+ internal fun `페이지 번호가 1보다 크면 이전 페이지가 존재한다`(number: Int) {
+ // given
+ val page = LoadMore(number)
+
+ // when
+ val actual = page.hasPrevious()
+
+ // then
+ assertThat(actual).isTrue
+ }
+
+ @Test
+ internal fun `페이지 번호가 1이면 이전 페이지가 존재하지 않는다`() {
+ // given
+ val page = LoadMore(1)
+
+ // when
+ val actual = page.hasPrevious()
+
+ // then
+ assertThat(actual).isFalse
+ }
+
+ @ParameterizedTest
+ @ValueSource(ints = [2, 3, 4, 5, 6, 100])
+ internal fun `페이지 번호를 증가시켰을 때, 1만큼 증가한다`(currentNumber: Int) {
+ // given
+ val page = LoadMore(currentNumber)
+ val expected = LoadMore(currentNumber + 1)
+
+ // when
+ val actual = page.next()
+
+ // then
+ assertThat(actual).usingRecursiveComparison().isEqualTo(expected)
+ }
+
+ @Test
+ internal fun `페이지에 해당하는 상품 목록을 가져온다`() {
+ // given
+ val page = LoadMore(2, 5)
+
+ // when
+ val actual = page.takeItems(ALL_UNCHECKED_CARTS)
+
+ // then
+ assertThat(actual).isEqualTo(ALL_UNCHECKED_CARTS.items.take(10))
+ }
+
+ @Test
+ internal fun `체크된 모든 상품 목록 개수를 반환한다`() {
+ // given
+ val page = LoadMore(2, 30)
+ val carts = ALL_CHECKED_CARTS
+
+ // when
+ val actual = page.getCheckedProductSize(carts)
+
+ // then
+ assertThat(actual).isEqualTo(60)
+ }
+}
diff --git a/domain/src/test/java/woowacourse/shopping/domain/model/PaginationTest.kt b/domain/src/test/java/woowacourse/shopping/domain/model/PaginationTest.kt
new file mode 100644
index 000000000..0b3bef987
--- /dev/null
+++ b/domain/src/test/java/woowacourse/shopping/domain/model/PaginationTest.kt
@@ -0,0 +1,122 @@
+package woowacourse.shopping.domain.model
+
+import org.assertj.core.api.Assertions.assertThat
+import org.junit.jupiter.api.Test
+import org.junit.jupiter.api.assertThrows
+import org.junit.jupiter.params.ParameterizedTest
+import org.junit.jupiter.params.provider.ValueSource
+import woowacourse.shopping.domain.fixture.ALL_CHECKED_CARTS
+import woowacourse.shopping.domain.fixture.ALL_UNCHECKED_CARTS
+import woowacourse.shopping.domain.fixture.getCart
+import woowacourse.shopping.domain.model.page.Pagination
+import woowacourse.shopping.domain.util.safeSubList
+
+internal class PaginationTest {
+ @Test
+ internal fun `첫 번째 페이지를 반환한다`() {
+ // given
+ val page = Pagination(3)
+
+ // when
+ val actual = page.getStartPage()
+
+ // then
+ assertThat(actual).usingRecursiveComparison().isEqualTo(Pagination(1))
+ }
+
+ @ParameterizedTest
+ @ValueSource(ints = [0, -1, -2, -3, -4, -5])
+ internal fun `페이지 번호가 1보다 작으면 예외가 발생한다`(number: Int) {
+ assertThrows { Pagination(number) }
+ }
+
+ @ParameterizedTest
+ @ValueSource(ints = [2, 3, 4, 5, 6, 100])
+ internal fun `페이지 번호가 1보다 크면 이전 페이지가 존재한다`(number: Int) {
+ // given
+ val page = Pagination(number)
+
+ // when
+ val actual = page.hasPrevious()
+
+ // then
+ assertThat(actual).isTrue
+ }
+
+ @Test
+ internal fun `페이지 번호가 1이면 이전 페이지가 존재하지 않는다`() {
+ // given
+ val page = Pagination(1)
+
+ // when
+ val actual = page.hasPrevious()
+
+ // then
+ assertThat(actual).isFalse
+ }
+
+ @Test
+ internal fun `총 카트 아이템 개수가 페이지가 수용 가능한 크기보다 크면, 다음 페이지가 존재한다`() {
+ // given
+ val page = Pagination(1, 5)
+ val cart = ALL_UNCHECKED_CARTS
+
+ // when
+ val actual = page.hasNext(cart)
+
+ // then
+ assertThat(actual).isTrue
+ }
+
+ @Test
+ internal fun `총 카트 아이템 개수가 페이지가 수용 가능한 크기보다 적으면, 다음 페이지가 존재하지 않는다`() {
+ // given
+ val page = Pagination(1, 5)
+ val cart = getCart(3)
+
+ // when
+ val actual = page.hasNext(cart)
+
+ // then
+ assertThat(actual).isFalse()
+ }
+
+ @ParameterizedTest
+ @ValueSource(ints = [2, 3, 4, 5, 6, 100])
+ internal fun `페이지 번호를 증가시켰을 때, 1만큼 증가한다`(currentNumber: Int) {
+ // given
+ val page = Pagination(currentNumber)
+ val expected = Pagination(currentNumber + 1)
+
+ // when
+ val actual = page.next()
+
+ // then
+ assertThat(actual).usingRecursiveComparison().isEqualTo(expected)
+ }
+
+ @Test
+ internal fun `페이지에 해당하는 상품 목록을 가져온다`() {
+ // given
+ val page = Pagination(2, 5)
+
+ // when
+ val actual = page.takeItems(ALL_UNCHECKED_CARTS)
+
+ // then
+ assertThat(actual).isEqualTo(ALL_UNCHECKED_CARTS.items.safeSubList(5, 10))
+ }
+
+ @Test
+ internal fun `현재 페이지에 체크된 모든 상품 목록 개수를 반환한다`() {
+ // given
+ val page = Pagination(2, 30)
+ val carts = ALL_CHECKED_CARTS
+
+ // when
+ val actual = page.getCheckedProductSize(carts)
+
+ // then
+ assertThat(actual).isEqualTo(30)
+ }
+}
diff --git a/domain/src/test/java/woowacourse/shopping/domain/PriceTest.kt b/domain/src/test/java/woowacourse/shopping/domain/model/PriceTest.kt
similarity index 71%
rename from domain/src/test/java/woowacourse/shopping/domain/PriceTest.kt
rename to domain/src/test/java/woowacourse/shopping/domain/model/PriceTest.kt
index 25f6cacc7..10df9e05c 100644
--- a/domain/src/test/java/woowacourse/shopping/domain/PriceTest.kt
+++ b/domain/src/test/java/woowacourse/shopping/domain/model/PriceTest.kt
@@ -1,7 +1,8 @@
-package woowacourse.shopping.domain
+package woowacourse.shopping.domain.model
import org.junit.jupiter.api.Test
import org.junit.jupiter.api.assertThrows
+import woowacourse.shopping.domain.model.Price
class PriceTest {
@Test
diff --git a/domain/src/test/java/woowacourse/shopping/domain/model/ProductCountTest.kt b/domain/src/test/java/woowacourse/shopping/domain/model/ProductCountTest.kt
new file mode 100644
index 000000000..2f9fbe6de
--- /dev/null
+++ b/domain/src/test/java/woowacourse/shopping/domain/model/ProductCountTest.kt
@@ -0,0 +1,30 @@
+package woowacourse.shopping.domain.model
+
+import org.junit.jupiter.api.Assertions.*
+import org.junit.jupiter.api.Test
+
+class ProductCountTest {
+ @Test
+ internal fun `제품 개수를 증가시킨다`() {
+ // given
+ val productCount = ProductCount(1, 1)
+
+ // when
+ val actual = productCount.plus(2)
+
+ // then
+ assertEquals(actual, ProductCount(3, 1))
+ }
+
+ @Test
+ internal fun `제품 개수를 감소시킬 때 최소 개수보다 줄어들 수 없다`() {
+ // given
+ val productCount = ProductCount(5, 1)
+
+ // when
+ val actual = productCount.minus(10)
+
+ // then
+ assertEquals(actual, ProductCount(1, 1))
+ }
+}
diff --git a/domain/src/test/java/woowacourse/shopping/domain/RecentProductsTest.kt b/domain/src/test/java/woowacourse/shopping/domain/model/RecentProductsTest.kt
similarity index 55%
rename from domain/src/test/java/woowacourse/shopping/domain/RecentProductsTest.kt
rename to domain/src/test/java/woowacourse/shopping/domain/model/RecentProductsTest.kt
index e97e71a29..4f39f3f4c 100644
--- a/domain/src/test/java/woowacourse/shopping/domain/RecentProductsTest.kt
+++ b/domain/src/test/java/woowacourse/shopping/domain/model/RecentProductsTest.kt
@@ -1,6 +1,8 @@
-package woowacourse.shopping.domain
+package woowacourse.shopping.domain.model
import org.assertj.core.api.Assertions
+import org.assertj.core.api.Assertions.assertThat
+import org.junit.jupiter.api.Test
import org.junit.jupiter.params.ParameterizedTest
import org.junit.jupiter.params.provider.CsvSource
@@ -21,4 +23,22 @@ internal class RecentProductsTest {
val actual = recentProducts.getItems().size
Assertions.assertThat(actual).isEqualTo(addCount.coerceAtMost(maxCount))
}
+
+ @Test
+ internal fun `마지막에 본 상품을 반환한다`() {
+ // given
+ var recentProducts = RecentProducts(maxCount = 10)
+
+ (1..10).forEach { id ->
+ val item = RecentProduct(0, Product(id, "상품 $id", Price(1000), ""))
+ recentProducts = recentProducts.add(item)
+ }
+ val expected = RecentProduct(0, Product(10, "상품 10", Price(1000), ""))
+
+ // when
+ val actual = recentProducts.getLatest()
+
+ // then
+ assertThat(actual).isEqualTo(expected)
+ }
}