diff --git a/app/src/androidTest/java/com/pabji/myfridge/ui/UITest.kt b/app/src/androidTest/java/com/pabji/myfridge/ui/UITest.kt
index e63ab5e..f36e11a 100644
--- a/app/src/androidTest/java/com/pabji/myfridge/ui/UITest.kt
+++ b/app/src/androidTest/java/com/pabji/myfridge/ui/UITest.kt
@@ -88,6 +88,8 @@ class UITest : KoinTest {
)
)
+ onView(withId(R.id.btn_add)).perform(click())
+
onView(isRoot()).perform(pressBack())
onView(withId(R.id.fab)).perform(click())
diff --git a/app/src/main/AndroidManifest.xml b/app/src/main/AndroidManifest.xml
index c700645..90f0aab 100644
--- a/app/src/main/AndroidManifest.xml
+++ b/app/src/main/AndroidManifest.xml
@@ -34,11 +34,10 @@
+ android:theme="@style/AppTheme.NoActionBar" />
-
+ android:name=".ui.barcode.BarcodeReaderActivity"
+ android:theme="@style/AppTheme.NoActionBar" />
\ No newline at end of file
diff --git a/app/src/main/java/com/pabji/myfridge/di/Modules.kt b/app/src/main/java/com/pabji/myfridge/di/Modules.kt
index a9cb871..512609e 100644
--- a/app/src/main/java/com/pabji/myfridge/di/Modules.kt
+++ b/app/src/main/java/com/pabji/myfridge/di/Modules.kt
@@ -68,8 +68,17 @@ val scopesModule = module {
}
scope(named()) {
- viewModel { (product: ItemProduct) -> ProductDetailViewModel(product, get(), get(), get()) }
+ viewModel { (product: ItemProduct) ->
+ ProductDetailViewModel(
+ product,
+ get(),
+ get(),
+ get(),
+ get()
+ )
+ }
scoped { GetProductDetail(get()) }
+ scoped { RemoveProduct(get()) }
scoped { SaveProduct(get()) }
}
diff --git a/app/src/main/java/com/pabji/myfridge/model/ItemProduct.kt b/app/src/main/java/com/pabji/myfridge/model/ItemProduct.kt
index 1cee01f..0e9745c 100644
--- a/app/src/main/java/com/pabji/myfridge/model/ItemProduct.kt
+++ b/app/src/main/java/com/pabji/myfridge/model/ItemProduct.kt
@@ -4,7 +4,6 @@ import com.pabji.domain.Product
import java.io.Serializable
data class ItemProduct(
- val id: Long? = null,
val name: String = "",
val previewUrl: String = "",
val existInFridge: Boolean = false,
@@ -13,7 +12,6 @@ data class ItemProduct(
fun Product.toItemProduct(): ItemProduct =
ItemProduct(
- id,
name,
previewUrl,
existInFridge,
@@ -21,4 +19,4 @@ fun Product.toItemProduct(): ItemProduct =
)
fun ItemProduct.toProduct(): Product =
- Product(id = id, name = name, barcode = barcode)
+ Product(name = name, barcode = barcode)
diff --git a/app/src/main/java/com/pabji/myfridge/model/database/RoomDataSource.kt b/app/src/main/java/com/pabji/myfridge/model/database/RoomDataSource.kt
index 3dce01d..4bd50cd 100644
--- a/app/src/main/java/com/pabji/myfridge/model/database/RoomDataSource.kt
+++ b/app/src/main/java/com/pabji/myfridge/model/database/RoomDataSource.kt
@@ -20,13 +20,6 @@ class RoomDataSource(database: RoomDatabase) : LocalDatasource {
} ?: Either.Left(DetailError)
}
- override suspend fun getProductById(productId: Long) =
- withContext(Dispatchers.IO) {
- productDao.getProductById(productId)?.run {
- Either.Right(toProduct())
- } ?: Either.Left(DetailError)
- }
-
override suspend fun getProductList() =
withContext(Dispatchers.IO) { productDao.getAll().map { it.toProduct() } }
diff --git a/app/src/main/java/com/pabji/myfridge/model/database/daos/ProductDao.kt b/app/src/main/java/com/pabji/myfridge/model/database/daos/ProductDao.kt
index 20d026f..a886562 100644
--- a/app/src/main/java/com/pabji/myfridge/model/database/daos/ProductDao.kt
+++ b/app/src/main/java/com/pabji/myfridge/model/database/daos/ProductDao.kt
@@ -9,9 +9,6 @@ interface ProductDao {
@Query("SELECT * FROM products")
suspend fun getAll(): List
- @Query("SELECT * FROM products WHERE id = :productId")
- suspend fun getProductById(productId: Long): ProductEntity?
-
@Query("SELECT * FROM products WHERE barcode = :barcode")
suspend fun getProductByBarcode(barcode: String): ProductEntity?
diff --git a/app/src/main/java/com/pabji/myfridge/model/database/entities/ProductEntity.kt b/app/src/main/java/com/pabji/myfridge/model/database/entities/ProductEntity.kt
index 67c940e..8f4de70 100644
--- a/app/src/main/java/com/pabji/myfridge/model/database/entities/ProductEntity.kt
+++ b/app/src/main/java/com/pabji/myfridge/model/database/entities/ProductEntity.kt
@@ -7,8 +7,7 @@ import com.pabji.myfridge.model.common.extensions.getListByDelimit
@Entity(tableName = "products")
data class ProductEntity(
- @PrimaryKey(autoGenerate = true) val id: Long?,
- val barcode: String,
+ @PrimaryKey val barcode: String,
val name: String,
val previewUrl: String,
val imageUrl: String,
@@ -25,7 +24,6 @@ data class ProductEntity(
internal fun ProductEntity.toProduct() =
Product(
- id,
barcode,
name,
previewUrl,
@@ -43,7 +41,6 @@ internal fun ProductEntity.toProduct() =
)
internal fun Product.toProductEntity() = ProductEntity(
- id,
barcode,
name,
previewUrl,
diff --git a/app/src/main/java/com/pabji/myfridge/ui/productDetail/ProductDetailActivity.kt b/app/src/main/java/com/pabji/myfridge/ui/productDetail/ProductDetailActivity.kt
index 6925ac2..c8d36f4 100644
--- a/app/src/main/java/com/pabji/myfridge/ui/productDetail/ProductDetailActivity.kt
+++ b/app/src/main/java/com/pabji/myfridge/ui/productDetail/ProductDetailActivity.kt
@@ -3,6 +3,7 @@ package com.pabji.myfridge.ui.productDetail
import android.os.Bundle
import android.widget.Toast
import androidx.appcompat.app.AppCompatActivity
+import androidx.core.content.ContextCompat
import androidx.lifecycle.Observer
import coil.api.load
import coil.size.Scale
@@ -10,7 +11,6 @@ import com.pabji.domain.Product
import com.pabji.myfridge.R
import com.pabji.myfridge.model.ItemProduct
import com.pabji.myfridge.ui.common.extensions.gone
-import com.pabji.myfridge.ui.common.extensions.setVisible
import com.pabji.myfridge.ui.common.extensions.visible
import com.pabji.myfridge.ui.productDetail.ProductDetailViewModel.UiModel
import kotlinx.android.synthetic.main.activity_product_detail.*
@@ -27,31 +27,58 @@ class ProductDetailActivity : AppCompatActivity() {
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_product_detail)
- setTitle(R.string.product_detail_title)
+
+ setSupportActionBar(toolbar)
+ supportActionBar?.setDisplayHomeAsUpEnabled(true)
+ supportActionBar?.setDisplayShowHomeEnabled(true)
viewModel.model.observe(this, Observer(::updateUI))
- btn_add.setOnClickListener { viewModel.onClickButtonAdd() }
+ btn_add.setOnClickListener { viewModel.onClickButton() }
+ }
+
+ override fun onSupportNavigateUp(): Boolean {
+ onBackPressed()
+ return true
}
private fun updateUI(viewState: UiModel?) {
when (viewState) {
- UiModel.Loading -> {
- }
- is UiModel.Content -> showProduct(viewState.product)
+ is UiModel.BasicContent -> showBasicProduct(viewState.product)
+ is UiModel.FullContent -> showProduct(viewState.product)
is UiModel.ProductSaved -> showSaved(viewState.product)
+ is UiModel.ProductRemoved -> showRemoved(viewState.product)
UiModel.Error -> showError()
}
}
+ private fun showBasicProduct(product: ItemProduct?) {
+ product?.run {
+ title = name
+ setButton(existInFridge)
+ setProductImage(previewUrl)
+ setProductName(name)
+ }
+ }
+
+ private fun showRemoved(product: Product) {
+ Toast.makeText(
+ this,
+ "${product.name} has been removed from your fridge",
+ Toast.LENGTH_SHORT
+ )
+ .show()
+ setButton(false)
+ }
+
private fun showSaved(product: Product) {
Toast.makeText(this, "${product.name} has been saved in your fridge", Toast.LENGTH_SHORT)
.show()
- btn_add.gone()
+ setButton(true)
}
private fun showProduct(product: Product) {
product.run {
- btn_add.setVisible(!existInFridge)
+ setButton(existInFridge)
setProductImage(imageUrl)
setProductName(name)
setGenericName(genericName)
@@ -61,10 +88,23 @@ class ProductDetailActivity : AppCompatActivity() {
}
}
+ private fun setButton(existInFridge: Boolean) {
+ if (existInFridge) {
+ btn_add.setBackgroundColor(ContextCompat.getColor(this, R.color.colorAccent))
+ btn_add.text = getString(R.string.remove_from_fridge)
+ } else {
+ btn_add.setBackgroundColor(ContextCompat.getColor(this, R.color.colorPrimary))
+ btn_add.text = getString(R.string.add_to_the_fridge)
+ }
+ btn_add.visible()
+ }
+
private fun setStores(stores: String) {
if (stores.isNotEmpty()) {
tv_stores.text = stores
ll_stores.visible()
+ } else {
+ ll_stores.gone()
}
}
@@ -72,12 +112,18 @@ class ProductDetailActivity : AppCompatActivity() {
if (categories.isNotEmpty()) {
tv_categories.text = categories
ll_categories.visible()
+ } else {
+ ll_categories.gone()
}
}
private fun setIngredientsText(ingredientsText: String) {
- tv_ingredients.text = ingredientsText
- ll_ingredients.visible()
+ if (ingredientsText.isNotEmpty()) {
+ tv_ingredients.text = ingredientsText
+ ll_ingredients.visible()
+ } else {
+ ll_ingredients.gone()
+ }
}
private fun setGenericName(genericName: String) {
diff --git a/app/src/main/java/com/pabji/myfridge/ui/productDetail/ProductDetailViewModel.kt b/app/src/main/java/com/pabji/myfridge/ui/productDetail/ProductDetailViewModel.kt
index 5fd4923..34152be 100644
--- a/app/src/main/java/com/pabji/myfridge/ui/productDetail/ProductDetailViewModel.kt
+++ b/app/src/main/java/com/pabji/myfridge/ui/productDetail/ProductDetailViewModel.kt
@@ -8,6 +8,7 @@ import com.pabji.myfridge.model.ItemProduct
import com.pabji.myfridge.model.toProduct
import com.pabji.myfridge.ui.common.BaseViewModel
import com.pabji.usecases.GetProductDetail
+import com.pabji.usecases.RemoveProduct
import com.pabji.usecases.SaveProduct
import kotlinx.coroutines.CoroutineDispatcher
import kotlinx.coroutines.launch
@@ -16,9 +17,11 @@ class ProductDetailViewModel(
private var itemProduct: ItemProduct?,
private val getProductDetail: GetProductDetail,
private val saveProduct: SaveProduct,
+ private val removeProduct: RemoveProduct,
uiDispatcher: CoroutineDispatcher
) : BaseViewModel(uiDispatcher) {
+ private var product: Product? = null
private val _model = MutableLiveData()
val model: LiveData
get() {
@@ -27,31 +30,41 @@ class ProductDetailViewModel(
}
sealed class UiModel {
- object Loading : UiModel()
- data class Content(val product: Product) : UiModel()
+ data class BasicContent(val product: ItemProduct?) : UiModel()
+ data class FullContent(val product: Product) : UiModel()
data class ProductSaved(val product: Product) : UiModel()
+ data class ProductRemoved(val product: Product) : UiModel()
object Error : UiModel()
}
- private fun loadProduct(product: ItemProduct?) {
+ private fun loadProduct(itemProduct: ItemProduct?) {
launch {
- _model.value = UiModel.Loading
- _model.value = product?.run {
+ _model.value = UiModel.BasicContent(itemProduct)
+ _model.value = itemProduct?.run {
getProductDetail(toProduct()).fold({
UiModel.Error
}, {
- UiModel.Content(it)
+ product = it
+ UiModel.FullContent(it)
})
} ?: UiModel.Error
}
}
- fun onClickButtonAdd() {
+ fun onClickButton() {
launch {
- when (val value = _model.value) {
- is UiModel.Content -> {
- saveProduct(value.product)
- _model.value = UiModel.ProductSaved(value.product)
+ product?.run {
+ _model.value = when {
+ existInFridge -> {
+ removeProduct(this)
+ existInFridge = false
+ UiModel.ProductRemoved(this)
+ }
+ else -> {
+ saveProduct(this)
+ existInFridge = true
+ UiModel.ProductSaved(this)
+ }
}
}
}
diff --git a/app/src/main/java/com/pabji/myfridge/ui/searchProducts/SearchProductsFragment.kt b/app/src/main/java/com/pabji/myfridge/ui/searchProducts/SearchProductsFragment.kt
index 5d09d21..6bcdaf1 100644
--- a/app/src/main/java/com/pabji/myfridge/ui/searchProducts/SearchProductsFragment.kt
+++ b/app/src/main/java/com/pabji/myfridge/ui/searchProducts/SearchProductsFragment.kt
@@ -32,6 +32,11 @@ class SearchProductsFragment : BaseFragment() {
setHasOptionsMenu(true)
}
+ override fun onResume() {
+ super.onResume()
+ viewModel.onSearch()
+ }
+
override fun onCreateView(
inflater: LayoutInflater,
container: ViewGroup?,
diff --git a/app/src/main/java/com/pabji/myfridge/ui/searchProducts/SearchProductsViewModel.kt b/app/src/main/java/com/pabji/myfridge/ui/searchProducts/SearchProductsViewModel.kt
index b724611..ea6626a 100644
--- a/app/src/main/java/com/pabji/myfridge/ui/searchProducts/SearchProductsViewModel.kt
+++ b/app/src/main/java/com/pabji/myfridge/ui/searchProducts/SearchProductsViewModel.kt
@@ -16,11 +16,7 @@ class SearchProductsViewModel(
) : BaseViewModel(uiDispatcher) {
private val _model = MutableLiveData()
- val model: LiveData
- get() {
- if (_model.value == null) onSearch()
- return _model
- }
+ val model: LiveData = _model
private val _navigation = MutableLiveData>()
val navigation: LiveData> = _navigation
diff --git a/app/src/main/res/layout/activity_product_detail.xml b/app/src/main/res/layout/activity_product_detail.xml
index 012e98c..7396bde 100644
--- a/app/src/main/res/layout/activity_product_detail.xml
+++ b/app/src/main/res/layout/activity_product_detail.xml
@@ -3,52 +3,160 @@
xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
- android:layout_height="match_parent">
+ android:layout_height="match_parent"
+ android:background="@color/lightgreen">
+
-
+
+
+
+
+ app:cardBackgroundColor="@color/white"
+ app:cardElevation="2dp"
+ app:cardUseCompatPadding="true"
+ app:layout_constraintHeight_percent="0.4"
+ app:layout_constraintTop_toBottomOf="@id/app_bar">
-
+ android:layout_height="match_parent">
+
+
+
+
+
+
+
+
+
+
-
+
-
+
+
+
+
+
+
+
+ app:layout_constraintBottom_toBottomOf="parent"
+ app:layout_constraintEnd_toStartOf="@id/gd_end"
+ app:layout_constraintStart_toEndOf="@id/gd_vertical2"
+ app:layout_constraintTop_toBottomOf="@id/gd_top3" />
+
+
+
+
+
+
+
+
+
@@ -135,14 +241,4 @@
-
-
diff --git a/app/src/main/res/layout/item_product_grid.xml b/app/src/main/res/layout/item_product_grid.xml
index fd5a3cd..a8bb7df 100644
--- a/app/src/main/res/layout/item_product_grid.xml
+++ b/app/src/main/res/layout/item_product_grid.xml
@@ -1,29 +1,39 @@
-
+ android:layout_height="wrap_content"
+ android:layout_margin="5dp">
-
+
-
+
-
\ No newline at end of file
+
+
+
+
+
\ No newline at end of file
diff --git a/app/src/main/res/layout/item_product_list.xml b/app/src/main/res/layout/item_product_list.xml
index ff443c9..657fb12 100644
--- a/app/src/main/res/layout/item_product_list.xml
+++ b/app/src/main/res/layout/item_product_list.xml
@@ -1,30 +1,38 @@
-
+ android:layout_height="wrap_content"
+ app:cardBackgroundColor="@color/white"
+ app:cardUseCompatPadding="true">
-
+
-
+
-
\ No newline at end of file
+
+
+
+
\ No newline at end of file
diff --git a/app/src/main/res/values/colors.xml b/app/src/main/res/values/colors.xml
index 69b2233..8de96e0 100644
--- a/app/src/main/res/values/colors.xml
+++ b/app/src/main/res/values/colors.xml
@@ -3,4 +3,8 @@
#008577
#00574B
#D81B60
+
+ #DEFFFB
+ #FFFFFF
+ #000000
diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml
index d6bbcb0..df100bf 100644
--- a/app/src/main/res/values/strings.xml
+++ b/app/src/main/res/values/strings.xml
@@ -18,4 +18,5 @@
Detalle
Tu nevera está vacía
Búsqueda no encontrada
+ Remove from fridge
diff --git a/app/src/test/java/com/pabji/myfridge/TestDI.kt b/app/src/test/java/com/pabji/myfridge/TestDI.kt
index bb12e43..1185816 100644
--- a/app/src/test/java/com/pabji/myfridge/TestDI.kt
+++ b/app/src/test/java/com/pabji/myfridge/TestDI.kt
@@ -4,7 +4,9 @@ import com.pabji.data.datasources.LocalDatasource
import com.pabji.data.datasources.RemoteDatasource
import com.pabji.domain.*
import com.pabji.myfridge.di.dataModule
+import com.pabji.testshared.mockedLocalProduct
import com.pabji.testshared.mockedLocalProductList
+import com.pabji.testshared.mockedProduct
import com.pabji.testshared.mockedRemoteProductList
import kotlinx.coroutines.Dispatchers
import org.koin.core.context.startKoin
@@ -25,67 +27,47 @@ private val mockedAppModule = module {
class FakeLocalDataSource : LocalDatasource {
- private var productList: MutableList = mockedLocalProductList.toMutableList()
var isError: Boolean = false
- fun reset() {
- productList.clear()
- }
-
override suspend fun getProductByBarcode(barcode: String?): Either =
if (isError) {
Either.Left(DetailError)
} else {
- productList.find { it.barcode == barcode }?.run {
- Either.Right(this)
- } ?: Either.Left(DetailError)
+ Either.Right(mockedLocalProduct)
}
- override suspend fun getProductById(productId: Long): Either =
- if (isError) {
- Either.Left(DetailError)
- } else {
- productList.find { it.id == productId }?.run {
- Either.Right(this)
- } ?: Either.Left(DetailError)
- }
+ override suspend fun getProductList(): List = if (isError) {
+ emptyList()
+ } else {
+ mockedLocalProductList
+ }
- override suspend fun getProductList(): List = productList
+ override suspend fun saveProduct(product: Product) {}
- override suspend fun saveProduct(product: Product) {
- productList.add(product)
- }
+ override suspend fun removeProduct(product: Product) {}
- override suspend fun removeProduct(product: Product) {
- productList.remove(product)
+ override suspend fun getProductsByTerm(searchTerm: String): List = if (isError) {
+ emptyList()
+ } else {
+ mockedLocalProductList
}
-
- override suspend fun getProductsByTerm(searchTerm: String): List =
- productList.filterIndexed { _, product ->
- product.name.startsWith(searchTerm)
- }
}
class FakeRemoteDataSource : RemoteDatasource {
- private var productList: MutableList = mockedRemoteProductList.toMutableList()
var isError: Boolean = false
override suspend fun searchProducts(searchTerm: String?): Either> =
if (isError) {
Either.Left(SearchError)
} else {
- Either.Right(productList.filterIndexed { _, product ->
- product.name.startsWith(searchTerm ?: "")
- })
+ Either.Right(mockedRemoteProductList)
}
override suspend fun getProductByBarcode(barcode: String): Either =
if (isError) {
Either.Left(DetailError)
} else {
- productList.find { it.barcode == barcode }?.run {
- Either.Right(this)
- } ?: Either.Left(DetailError)
+ Either.Right(mockedProduct)
}
}
diff --git a/app/src/test/java/com/pabji/myfridge/ui/barcode/BarcodeReaderIntegrationTest.kt b/app/src/test/java/com/pabji/myfridge/ui/barcode/BarcodeReaderIntegrationTest.kt
index ccf76f7..22b38aa 100644
--- a/app/src/test/java/com/pabji/myfridge/ui/barcode/BarcodeReaderIntegrationTest.kt
+++ b/app/src/test/java/com/pabji/myfridge/ui/barcode/BarcodeReaderIntegrationTest.kt
@@ -3,12 +3,14 @@ package com.pabji.myfridge.ui.barcode
import androidx.arch.core.executor.testing.InstantTaskExecutorRule
import androidx.lifecycle.Observer
import com.nhaarman.mockitokotlin2.verify
+import com.pabji.data.datasources.LocalDatasource
import com.pabji.data.datasources.RemoteDatasource
+import com.pabji.myfridge.FakeLocalDataSource
import com.pabji.myfridge.FakeRemoteDataSource
import com.pabji.myfridge.initMockedDi
import com.pabji.myfridge.model.toItemProduct
import com.pabji.testshared.mockedBarcodeList
-import com.pabji.testshared.mockedRemoteProductList
+import com.pabji.testshared.mockedLocalProductList
import com.pabji.usecases.SearchProductsByBarcode
import org.junit.Before
import org.junit.Rule
@@ -33,6 +35,8 @@ class BarcodeReaderIntegrationTest : AutoCloseKoinTest() {
private lateinit var remoteDataSource: FakeRemoteDataSource
+ private lateinit var localDataSource: FakeLocalDataSource
+
@Before
fun setUp() {
val vmModule = module {
@@ -42,6 +46,7 @@ class BarcodeReaderIntegrationTest : AutoCloseKoinTest() {
initMockedDi(vmModule)
remoteDataSource = get() as FakeRemoteDataSource
+ localDataSource = get() as FakeLocalDataSource
vm = get()
}
@@ -50,13 +55,14 @@ class BarcodeReaderIntegrationTest : AutoCloseKoinTest() {
vm.model.observeForever(uiModelObserver)
vm.onBarcodeDetected(mockedBarcodeList)
verify(uiModelObserver).onChanged(
- BarcodeReaderViewModel.UiModel.Content(mockedRemoteProductList.map { it.toItemProduct() })
+ BarcodeReaderViewModel.UiModel.Content(mockedLocalProductList.map { it.toItemProduct() })
)
}
@Test
fun `when barcode list detected, error is shown`() {
remoteDataSource.isError = true
+ localDataSource.isError = true
vm.model.observeForever(uiModelObserver)
vm.onBarcodeDetected(mockedBarcodeList)
verify(uiModelObserver).onChanged(
diff --git a/app/src/test/java/com/pabji/myfridge/ui/productDetail/ProductDetailIntegrationTest.kt b/app/src/test/java/com/pabji/myfridge/ui/productDetail/ProductDetailIntegrationTest.kt
index 426eb9b..ffc5158 100644
--- a/app/src/test/java/com/pabji/myfridge/ui/productDetail/ProductDetailIntegrationTest.kt
+++ b/app/src/test/java/com/pabji/myfridge/ui/productDetail/ProductDetailIntegrationTest.kt
@@ -12,6 +12,7 @@ import com.pabji.myfridge.model.toItemProduct
import com.pabji.testshared.mockedLocalProduct
import com.pabji.testshared.mockedProduct
import com.pabji.usecases.GetProductDetail
+import com.pabji.usecases.RemoveProduct
import com.pabji.usecases.SaveProduct
import org.junit.Before
import org.junit.Rule
@@ -43,9 +44,10 @@ class ProductDetailIntegrationTest : AutoCloseKoinTest() {
@Before
fun setUp() {
val vmModule = module {
- factory { ProductDetailViewModel(mockedItemProduct, get(), get(), get()) }
+ factory { ProductDetailViewModel(mockedItemProduct, get(), get(), get(), get()) }
factory { GetProductDetail(get()) }
factory { SaveProduct(get()) }
+ factory { RemoveProduct(get()) }
}
initMockedDi(vmModule)
@@ -57,14 +59,18 @@ class ProductDetailIntegrationTest : AutoCloseKoinTest() {
@Test
fun `when local product exist, product is shown`() {
vm.model.observeForever(uiModelObserver)
- verify(uiModelObserver).onChanged(ProductDetailViewModel.UiModel.Content(mockedLocalProduct))
+ verify(uiModelObserver).onChanged(
+ ProductDetailViewModel.UiModel.FullContent(
+ mockedLocalProduct
+ )
+ )
}
@Test
fun `when local product doesnt exist, remote product is shown`() {
- localDataSource.reset()
+ localDataSource.isError = true
vm.model.observeForever(uiModelObserver)
- verify(uiModelObserver).onChanged(ProductDetailViewModel.UiModel.Content(mockedProduct))
+ verify(uiModelObserver).onChanged(ProductDetailViewModel.UiModel.FullContent(mockedProduct))
}
@Test
@@ -76,10 +82,24 @@ class ProductDetailIntegrationTest : AutoCloseKoinTest() {
}
@Test
- fun `when save product, product saved state is shown`() {
- localDataSource.reset()
+ fun `when click in button, product saved state is shown`() {
+ vm.model.observeForever(uiModelObserver)
+ vm.onClickButton()
+ verify(uiModelObserver).onChanged(
+ ProductDetailViewModel.UiModel.ProductSaved(
+ mockedLocalProduct
+ )
+ )
+ }
+
+ @Test
+ fun `when click in button, product remove state is shown`() {
vm.model.observeForever(uiModelObserver)
- vm.onClickButtonAdd()
- verify(uiModelObserver).onChanged(ProductDetailViewModel.UiModel.ProductSaved(mockedProduct))
+ vm.onClickButton()
+ verify(uiModelObserver).onChanged(
+ ProductDetailViewModel.UiModel.ProductRemoved(
+ mockedLocalProduct
+ )
+ )
}
}
diff --git a/app/src/test/java/com/pabji/myfridge/ui/productDetail/ProductDetailViewModelTest.kt b/app/src/test/java/com/pabji/myfridge/ui/productDetail/ProductDetailViewModelTest.kt
index 90b4bc2..69b8819 100644
--- a/app/src/test/java/com/pabji/myfridge/ui/productDetail/ProductDetailViewModelTest.kt
+++ b/app/src/test/java/com/pabji/myfridge/ui/productDetail/ProductDetailViewModelTest.kt
@@ -10,6 +10,7 @@ import com.pabji.domain.Either
import com.pabji.myfridge.model.toItemProduct
import com.pabji.testshared.mockedProduct
import com.pabji.usecases.GetProductDetail
+import com.pabji.usecases.RemoveProduct
import com.pabji.usecases.SaveProduct
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.runBlocking
@@ -32,6 +33,9 @@ class ProductDetailViewModelTest {
@Mock
lateinit var saveProduct: SaveProduct
+ @Mock
+ lateinit var removeProduct: RemoveProduct
+
@Mock
lateinit var uiModelObserver: Observer
@@ -45,6 +49,7 @@ class ProductDetailViewModelTest {
mockedItemProduct,
getProductDetail,
saveProduct,
+ removeProduct,
Dispatchers.Unconfined
)
}
@@ -54,7 +59,11 @@ class ProductDetailViewModelTest {
runBlocking {
whenever(getProductDetail(any())).thenReturn(Either.Right(mockedProduct))
vm.model.observeForever(uiModelObserver)
- verify(uiModelObserver).onChanged(ProductDetailViewModel.UiModel.Content(mockedProduct))
+ verify(uiModelObserver).onChanged(
+ ProductDetailViewModel.UiModel.FullContent(
+ mockedProduct
+ )
+ )
}
}
@@ -72,7 +81,7 @@ class ProductDetailViewModelTest {
runBlocking {
whenever(getProductDetail(any())).thenReturn(Either.Right(mockedProduct))
vm.model.observeForever(uiModelObserver)
- vm.onClickButtonAdd()
+ vm.onClickButton()
verify(uiModelObserver).onChanged(
ProductDetailViewModel.UiModel.ProductSaved(
mockedProduct
@@ -83,7 +92,13 @@ class ProductDetailViewModelTest {
@Test
fun `while observing Model LiveData snf Itemproduct is null, error is shown`() {
- val vm = ProductDetailViewModel(null, getProductDetail, saveProduct, Dispatchers.Unconfined)
+ val vm = ProductDetailViewModel(
+ null,
+ getProductDetail,
+ saveProduct,
+ removeProduct,
+ Dispatchers.Unconfined
+ )
runBlocking {
vm.model.observeForever(uiModelObserver)
verify(uiModelObserver).onChanged(ProductDetailViewModel.UiModel.Error)
diff --git a/app/src/test/java/com/pabji/myfridge/ui/productList/ProductListIntegrationTest.kt b/app/src/test/java/com/pabji/myfridge/ui/productList/ProductListIntegrationTest.kt
index 48dbc53..d2992e7 100644
--- a/app/src/test/java/com/pabji/myfridge/ui/productList/ProductListIntegrationTest.kt
+++ b/app/src/test/java/com/pabji/myfridge/ui/productList/ProductListIntegrationTest.kt
@@ -46,7 +46,7 @@ class ProductListIntegrationTest : AutoCloseKoinTest() {
@Test
fun `when local is empty, should show empty product list state`() {
- localDataSource.reset()
+ localDataSource.isError = true
vm.model.observeForever(uiModelObserver)
vm.updateData()
verify(uiModelObserver).onChanged(ProductListViewModel.UiModel.EmptyList)
diff --git a/app/src/test/java/com/pabji/myfridge/ui/searchProducts/SearchProductsIntegrationTest.kt b/app/src/test/java/com/pabji/myfridge/ui/searchProducts/SearchProductsIntegrationTest.kt
index 1e0a4a6..f15adac 100644
--- a/app/src/test/java/com/pabji/myfridge/ui/searchProducts/SearchProductsIntegrationTest.kt
+++ b/app/src/test/java/com/pabji/myfridge/ui/searchProducts/SearchProductsIntegrationTest.kt
@@ -2,7 +2,6 @@ package com.pabji.myfridge.ui.searchProducts
import androidx.arch.core.executor.testing.InstantTaskExecutorRule
import androidx.lifecycle.Observer
-import com.nhaarman.mockitokotlin2.times
import com.nhaarman.mockitokotlin2.verify
import com.pabji.data.datasources.LocalDatasource
import com.pabji.data.datasources.RemoteDatasource
@@ -55,36 +54,37 @@ class SearchProductsIntegrationTest : AutoCloseKoinTest() {
@Test
fun `when init, remote random list should be shown`() {
- localDataSource.reset()
+ localDataSource.isError = true
vm.model.observeForever(uiModelObserver)
+ vm.onSearch("")
verify(uiModelObserver)
.onChanged(UiModel.Content(mockedRemoteProductList.map { it.toItemProduct() }))
}
@Test
fun `when empty term sended, empty list should be shown`() {
- localDataSource.reset()
remoteDataSource.isError = true
+ localDataSource.isError = true
vm.model.observeForever(uiModelObserver)
vm.onSearch("")
- verify(uiModelObserver, times(2)).onChanged(UiModel.EmptyList)
+ verify(uiModelObserver).onChanged(UiModel.EmptyList)
}
@Test
fun `with search term, product list should be shown`() {
vm.model.observeForever(uiModelObserver)
vm.onSearch("Product")
- verify(uiModelObserver, times(2))
+ verify(uiModelObserver)
.onChanged(UiModel.Content(mockedFilteredProducts.map { it.toItemProduct() }))
}
@Test
fun `with search term and remote returns error, empty product list should be shown`() {
- localDataSource.reset()
remoteDataSource.isError = true
+ localDataSource.isError = true
vm.model.observeForever(uiModelObserver)
vm.onSearch("Product")
- verify(uiModelObserver, times(2)).onChanged(UiModel.EmptyList)
+ verify(uiModelObserver).onChanged(UiModel.EmptyList)
}
@Test
@@ -92,7 +92,7 @@ class SearchProductsIntegrationTest : AutoCloseKoinTest() {
remoteDataSource.isError = true
vm.model.observeForever(uiModelObserver)
vm.onSearch("Product")
- verify(uiModelObserver, times(2))
+ verify(uiModelObserver)
.onChanged(UiModel.Content(mockedLocalProductList.map { it.toItemProduct() }))
}
}
diff --git a/app/src/test/java/com/pabji/myfridge/ui/searchProducts/SearchProductsViewModelTest.kt b/app/src/test/java/com/pabji/myfridge/ui/searchProducts/SearchProductsViewModelTest.kt
index 43ceb61..c267c73 100644
--- a/app/src/test/java/com/pabji/myfridge/ui/searchProducts/SearchProductsViewModelTest.kt
+++ b/app/src/test/java/com/pabji/myfridge/ui/searchProducts/SearchProductsViewModelTest.kt
@@ -47,6 +47,7 @@ class SearchProductsViewModelTest {
runBlocking {
whenever(searchProductsByTerm.invoke(any())).thenReturn(emptyList())
vm.model.observeForever(uiModelObserver)
+ vm.onSearch("")
verify(uiModelObserver).onChanged(UiModel.EmptyList)
}
}
@@ -57,6 +58,7 @@ class SearchProductsViewModelTest {
val itemProducts = mockedRemoteProductList.map { it.toItemProduct() }
whenever(searchProductsByTerm.invoke(any())).thenReturn(mockedRemoteProductList)
vm.model.observeForever(uiModelObserver)
+ vm.onSearch("")
verify(uiModelObserver).onChanged(UiModel.Content(itemProducts))
}
}
diff --git a/build.gradle b/build.gradle
index 2ed106c..6d7c303 100644
--- a/build.gradle
+++ b/build.gradle
@@ -38,6 +38,7 @@ allprojects {
multidex : 'com.android.support:multidex:1.0.3',
appcompat : 'androidx.appcompat:appcompat:1.1.0',
androidCore : 'androidx.core:core-ktx:1.1.0',
+ cardView : 'androidx.cardview:cardview:1.0.0',
constraintLayout : 'androidx.constraintlayout:constraintlayout:1.1.3',
materialDesign : 'com.google.android.material:material:1.0.0',
lifecycleExtensions: "androidx.lifecycle:lifecycle-extensions:2.2.0",
diff --git a/data/src/main/java/com/pabji/data/datasources/LocalDatasource.kt b/data/src/main/java/com/pabji/data/datasources/LocalDatasource.kt
index 48a2395..2e2e893 100644
--- a/data/src/main/java/com/pabji/data/datasources/LocalDatasource.kt
+++ b/data/src/main/java/com/pabji/data/datasources/LocalDatasource.kt
@@ -6,7 +6,6 @@ import com.pabji.domain.Product
interface LocalDatasource {
suspend fun getProductByBarcode(barcode: String?): Either
- suspend fun getProductById(productId: Long): Either
suspend fun getProductList(): List
suspend fun saveProduct(product: Product)
suspend fun removeProduct(product: Product)
diff --git a/data/src/main/java/com/pabji/data/repositories/ProductRepositoryImpl.kt b/data/src/main/java/com/pabji/data/repositories/ProductRepositoryImpl.kt
index 2561aff..321e006 100644
--- a/data/src/main/java/com/pabji/data/repositories/ProductRepositoryImpl.kt
+++ b/data/src/main/java/com/pabji/data/repositories/ProductRepositoryImpl.kt
@@ -16,8 +16,12 @@ class ProductRepositoryImpl(
override suspend fun removeProduct(product: Product) = localDataSource.removeProduct(product)
override suspend fun searchProducts(searchTerm: String?): List {
-
- val localProducts = localDataSource.getProductsByTerm(searchTerm ?: "")
+ val localProducts =
+ if (searchTerm.isNullOrEmpty()) {
+ localDataSource.getProductList()
+ } else {
+ localDataSource.getProductsByTerm(searchTerm)
+ }
val remoteProducts = remoteDataSource.searchProducts(searchTerm)
.map { it.getFilteredProductsByProducts(localProducts) }
@@ -27,16 +31,9 @@ class ProductRepositoryImpl(
}
override suspend fun getProductDetail(product: Product): Either =
- with(localDataSource.getProductById(product.id ?: 0)) {
+ with(localDataSource.getProductByBarcode(product.barcode)) {
if (isLeft) {
- val barcode = product.barcode
- with(localDataSource.getProductByBarcode(barcode)) {
- if (isLeft) {
- remoteDataSource.getProductByBarcode(barcode)
- } else {
- this
- }
- }
+ remoteDataSource.getProductByBarcode(product.barcode)
} else {
this
}
@@ -44,8 +41,10 @@ class ProductRepositoryImpl(
override suspend fun searchProductsByBarcode(barcodeList: List): List =
barcodeList
- .mapNotNull {
- remoteDataSource.getProductByBarcode(it).fold({ null }) { product -> product }
+ .mapNotNull { barcode ->
+ localDataSource.getProductByBarcode(barcode).fold({ null }) { product -> product }
+ ?: remoteDataSource.getProductByBarcode(barcode)
+ .fold({ null }) { product -> product }
}
}
diff --git a/data/src/test/java/com/pabji/data/repositories/ProductRepositoryTest.kt b/data/src/test/java/com/pabji/data/repositories/ProductRepositoryTest.kt
index 00097f4..2b00e41 100644
--- a/data/src/test/java/com/pabji/data/repositories/ProductRepositoryTest.kt
+++ b/data/src/test/java/com/pabji/data/repositories/ProductRepositoryTest.kt
@@ -93,11 +93,11 @@ class ProductRepositoryTest {
}
@Test
- fun `when searchProduct without should return productList`() {
+ fun `when searchProduct without term should return productList`() {
runBlocking {
val remoteProductList = mockedRemoteProductList
- whenever(localDataSource.getProductsByTerm("")).thenReturn(emptyList())
+ whenever(localDataSource.getProductList()).thenReturn(emptyList())
whenever(remoteDatasource.searchProducts(null)).thenReturn(
Either.Right(
remoteProductList
@@ -106,34 +106,18 @@ class ProductRepositoryTest {
val result = productRepository.searchProducts()
- verify(localDataSource).getProductsByTerm("")
+ verify(localDataSource).getProductList()
verify(remoteDatasource).searchProducts(null)
assertEquals(remoteProductList, result)
}
}
- @Test
- fun `when getProductDetail with id should return product from localDatasource`() {
- runBlocking {
- val product = mockedProduct.copy(id = 1)
-
- whenever(localDataSource.getProductById(1)).thenReturn(Either.Right(product))
-
- val result = productRepository.getProductDetail(product)
-
- verify(localDataSource).getProductById(1)
- assertTrue(result.isRight)
- assertEquals(Either.Right(product), result)
- }
- }
-
@Test
fun `when getProductDetail with barcode should return product from localDatasource`() {
runBlocking {
val mockedBarcode = "123456"
val product = mockedLocalProduct.copy(barcode = mockedBarcode)
- whenever(localDataSource.getProductById(any())).thenReturn(Either.Left(DetailError))
whenever(localDataSource.getProductByBarcode(mockedBarcode)).thenReturn(
Either.Right(product)
)
@@ -151,7 +135,6 @@ class ProductRepositoryTest {
runBlocking {
val mockedBarcode = "123456"
val product = mockedLocalProduct.copy(barcode = mockedBarcode)
- whenever(localDataSource.getProductById(any())).thenReturn(Either.Left(DetailError))
whenever(localDataSource.getProductByBarcode(any())).thenReturn(Either.Left(DetailError))
whenever(remoteDatasource.getProductByBarcode(mockedBarcode)).thenReturn(
Either.Right(product)
@@ -173,7 +156,6 @@ class ProductRepositoryTest {
val product = mockedLocalProduct.copy()
- whenever(localDataSource.getProductById(any())).thenReturn(Either.Left(DetailError))
whenever(localDataSource.getProductByBarcode(any())).thenReturn(Either.Left(DetailError))
whenever(remoteDatasource.getProductByBarcode(any())).thenReturn(Either.Left(DetailError))
@@ -187,13 +169,31 @@ class ProductRepositoryTest {
}
@Test
- fun `when searchProductsByBarcode with barcodeList should return product list`() {
+ fun `when searchProductsByBarcode with barcodeList should return product list from localDatasource`() {
runBlocking {
+ whenever(localDataSource.getProductByBarcode(any())).thenReturn(
+ Either.Right(mockedProduct)
+ )
+
+ val result = productRepository.searchProductsByBarcode(mockedBarcodeList)
+
+ verify(localDataSource, times(mockedBarcodeList.size)).getProductByBarcode(any())
+
+ assertTrue(result.isNotEmpty())
+ }
+ }
+
+ @Test
+ fun `when searchProductsByBarcode with barcodeList should return product list from remoteDatasource`() {
+ runBlocking {
+
+ whenever(localDataSource.getProductByBarcode(any())).thenReturn(
+ Either.Left(SearchError)
+ )
+
whenever(remoteDatasource.getProductByBarcode(any())).thenReturn(
- Either.Right(
- mockedProduct
- )
+ Either.Right(mockedProduct)
)
val result = productRepository.searchProductsByBarcode(mockedBarcodeList)
@@ -208,6 +208,7 @@ class ProductRepositoryTest {
fun `when searchProductsByBarcode with barcodeList should return empty list`() {
runBlocking {
+ whenever(localDataSource.getProductByBarcode(any())).thenReturn(Either.Left(SearchError))
whenever(remoteDatasource.getProductByBarcode(any())).thenReturn(Either.Left(SearchError))
val result = productRepository.searchProductsByBarcode(mockedBarcodeList)
diff --git a/domain/src/main/java/com/pabji/domain/Product.kt b/domain/src/main/java/com/pabji/domain/Product.kt
index 13e8585..2dc58e3 100644
--- a/domain/src/main/java/com/pabji/domain/Product.kt
+++ b/domain/src/main/java/com/pabji/domain/Product.kt
@@ -1,7 +1,6 @@
package com.pabji.domain
data class Product(
- val id: Long? = null,
val barcode: String = "",
val name: String,
val previewUrl: String = "",
@@ -15,5 +14,5 @@ data class Product(
val ingredientsText: String = "",
val imageIngredientsUrl: String = "",
val categories: String = "",
- val existInFridge: Boolean = false
+ var existInFridge: Boolean = false
)
diff --git a/testShared/src/main/java/com/pabji/testshared/Mocks.kt b/testShared/src/main/java/com/pabji/testshared/Mocks.kt
index 2c8213b..7ab1bab 100644
--- a/testShared/src/main/java/com/pabji/testshared/Mocks.kt
+++ b/testShared/src/main/java/com/pabji/testshared/Mocks.kt
@@ -6,7 +6,7 @@ val mockedBarcodeList = listOf("1234567", "7654321")
val mockedProduct = Product(barcode = "1234567", name = "ProductName")
-val mockedLocalProduct = mockedProduct.copy(id = 1, existInFridge = true)
+val mockedLocalProduct = mockedProduct.copy(existInFridge = true)
val mockedLocalProductList = listOf(
mockedLocalProduct.copy()
@@ -18,6 +18,6 @@ val mockedRemoteProductList = listOf(
)
val mockedFilteredProducts = listOf(
- mockedLocalProduct.copy(1),
+ mockedLocalProduct.copy(),
mockedProduct.copy(name = "Product2", barcode = "7654321")
)
diff --git a/usecases/src/test/java/com/pabji/usecases/GetProductDetailTest.kt b/usecases/src/test/java/com/pabji/usecases/GetProductDetailTest.kt
index a0372d8..60a59ce 100644
--- a/usecases/src/test/java/com/pabji/usecases/GetProductDetailTest.kt
+++ b/usecases/src/test/java/com/pabji/usecases/GetProductDetailTest.kt
@@ -32,7 +32,7 @@ class GetProductDetailTest {
@Test
fun `when invoke should return product`() {
runBlocking {
- val product = mockedProduct.copy(id = 1)
+ val product = mockedProduct.copy()
whenever(productRepository.getProductDetail(product)).thenReturn(Either.Right(product))
val result = getProductDetail(product)
diff --git a/usecases/src/test/java/com/pabji/usecases/RemoveProductTest.kt b/usecases/src/test/java/com/pabji/usecases/RemoveProductTest.kt
index 7fb7071..68ec818 100644
--- a/usecases/src/test/java/com/pabji/usecases/RemoveProductTest.kt
+++ b/usecases/src/test/java/com/pabji/usecases/RemoveProductTest.kt
@@ -26,7 +26,7 @@ class RemoveProductTest {
@Test
fun `when invoke should call removeProduct from repository`() {
runBlocking {
- val product = mockedProduct.copy(id = 1)
+ val product = mockedProduct.copy()
removeProduct(product)
verify(productRepository).removeProduct(product)
}
diff --git a/usecases/src/test/java/com/pabji/usecases/SaveProductTest.kt b/usecases/src/test/java/com/pabji/usecases/SaveProductTest.kt
index f921ef9..e5ff246 100644
--- a/usecases/src/test/java/com/pabji/usecases/SaveProductTest.kt
+++ b/usecases/src/test/java/com/pabji/usecases/SaveProductTest.kt
@@ -26,7 +26,7 @@ class SaveProductTest {
@Test
fun `when invoke should call saveProduct from repository`() {
runBlocking {
- val product = mockedProduct.copy(id = 1)
+ val product = mockedProduct.copy()
saveProduct(product)
verify(productRepository).saveProduct(product)
}