Skip to content
Merged
Show file tree
Hide file tree
Changes from 15 commits
Commits
Show all changes
21 commits
Select commit Hold shift + click to select a range
823fc96
ALFMOB-81 Add product to bag and bag list
mrfreitas Jan 28, 2025
4edd25c
Merge branch 'dev/Fixes_on_mapping' into dev/ALFMOB-81_add_to_bag
mrfreitas Jan 28, 2025
c60bfa3
Merge branch 'refs/heads/main' into dev/ALFMOB-81_add_to_bag
mrfreitas Jan 30, 2025
b070c89
Added unit test for use cases
mrfreitas Jan 30, 2025
8e8afad
Added unit test for ViewModel
mrfreitas Jan 31, 2025
b910e64
Merge branch 'main' into dev/ALFMOB-81_add_to_bag
mrfreitas Jan 31, 2025
9ff9a6b
Multiple improvements
mrfreitas Feb 6, 2025
92a989f
Merge branch 'main' into dev/ALFMOB-81_add_to_bag
mrfreitas Feb 6, 2025
30691b6
Multiple improvements
mrfreitas Feb 6, 2025
ada3510
chore: Add to bag fixed, unit tests fixed and static code analysis fixed
QaziMashhood Feb 14, 2025
10fb65d
chore: passing of state removed from AddtoBag event
QaziMashhood Feb 16, 2025
ea5e467
chore: passing of state removed from AddtoBag event
QaziMashhood Feb 16, 2025
151572a
feature: remove from bag functionality added
QaziMashhood Feb 17, 2025
35fe894
chore: fixed tests
QaziMashhood Feb 17, 2025
28a2b5d
chore: fixed tests
QaziMashhood Feb 17, 2025
bff7390
chore: Refactoring an code improvements
QaziMashhood Feb 24, 2025
f9b44d8
feature: [ALFMOB-81] changes due to feedback
Mar 13, 2025
a122bc5
feature: [ALFMOB-81] cleanup and fix unit tests
rsousamindera Mar 13, 2025
681e038
feature: [ALFMOB-81] cleanup
rsousamindera Mar 14, 2025
e240a46
Merge branch 'main' into dev/ALFMOB-81_add_to_bag
rsousamindera Mar 14, 2025
aec4884
Merge branch 'main' into dev/ALFMOB-81_add_to_bag
artyomromanov Jan 14, 2026
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
@@ -0,0 +1,38 @@
package au.com.alfie.ecomm.data.bag

import au.com.alfie.ecomm.data.toRepositoryResult
import au.com.alfie.ecomm.repository.bag.BagProduct
import au.com.alfie.ecomm.repository.bag.BagRepository
import au.com.alfie.ecomm.repository.result.RepositoryResult
import kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.flow.MutableStateFlow
import kotlinx.coroutines.flow.map
import javax.inject.Inject

class BagRepositoryImpl @Inject constructor() : BagRepository {

// TODO consider removing this property when the products in the bag are saved on database or api
private val _bag = MutableStateFlow<List<BagProduct>>(listOf())

// TODO change this implementation to a proper implementation using data base or api to save the product
override fun addToBag(bagProduct: BagProduct): RepositoryResult<Boolean> {
_bag.value.firstOrNull {
bagProduct.productId == it.productId && bagProduct.variantSku == it.variantSku
} ?: run {
_bag.value = _bag.value.toMutableList().apply { add(bagProduct) }
}
return RepositoryResult.Success(true)
}

// TODO change this implementation to a proper implementation using data base or api to get the products in the bag
override fun getBag(): Flow<RepositoryResult<List<BagProduct>>> {
return _bag.map { bag ->
Result.success(bag).toRepositoryResult()
}
}

override fun removeFromBag(bagProduct: BagProduct): RepositoryResult<Boolean> {
_bag.value = _bag.value.toMutableList().apply { remove(bagProduct) }
return RepositoryResult.Success(true)
}
}
18 changes: 18 additions & 0 deletions data/src/main/java/au/com/alfie/ecomm/data/bag/di/BagModule.kt
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
package au.com.alfie.ecomm.data.bag.di

import au.com.alfie.ecomm.data.bag.BagRepositoryImpl
import au.com.alfie.ecomm.repository.bag.BagRepository
import dagger.Binds
import dagger.Module
import dagger.hilt.InstallIn
import dagger.hilt.components.SingletonComponent
import javax.inject.Singleton

@Module
@InstallIn(SingletonComponent::class)
internal abstract class BagModule {

@Binds
@Singleton
abstract fun bindBagRepository(bagRepositoryImpl: BagRepositoryImpl): BagRepository
}
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,7 @@ sealed interface ProductCardType {
override val brand: String,
override val name: String,
override val price: PriceType,
val onRemoveClick: ClickEvent? = null,
val color: String,
val size: String,
override val cardTestTag: String = PRODUCT_CARD,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -7,10 +7,13 @@ import androidx.compose.foundation.layout.Spacer
import androidx.compose.foundation.layout.fillMaxWidth
import androidx.compose.foundation.layout.size
import androidx.compose.foundation.layout.width
import androidx.compose.material3.Icon
import androidx.compose.material3.IconButton
import androidx.compose.material3.Text
import androidx.compose.runtime.Composable
import androidx.compose.ui.Modifier
import androidx.compose.ui.platform.testTag
import androidx.compose.ui.res.painterResource
import androidx.compose.ui.res.stringResource
import androidx.compose.ui.text.style.TextOverflow
import androidx.compose.ui.tooling.preview.Preview
Expand Down Expand Up @@ -137,6 +140,18 @@ internal fun ProductCardXSmall(
.testTag(productCard.priceTestTag)
)
}
if (isLoading.not() && productCard.onRemoveClick != null) {
IconButton(
modifier = Modifier.size(Theme.iconSize.large),
onClick = productCard.onRemoveClick
) {
Icon(
painter = painterResource(id = R.drawable.ic_action_close_dark),
contentDescription = null,
modifier = Modifier.size(Theme.iconSize.small)
)
}
}
}
}

Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
package au.com.alfie.ecomm.repository.bag

data class BagProduct(
val productId: String,
val variantSku: String
)
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
package au.com.alfie.ecomm.repository.bag

import au.com.alfie.ecomm.repository.result.RepositoryResult
import kotlinx.coroutines.flow.Flow

interface BagRepository {

fun addToBag(bagProduct: BagProduct): RepositoryResult<Boolean>

fun getBag(): Flow<RepositoryResult<List<BagProduct>>>

fun removeFromBag(bagProduct: BagProduct): RepositoryResult<Boolean>
}
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@ import au.com.alfie.ecomm.repository.shared.model.Size
data class Variant(
val attributes: List<Attribute>,
val color: Color?,
val media: Media,
val media: Media.Image,
val price: Price,
val size: Size?,
val sku: String,
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
package au.com.alfie.ecomm.domain.usecase.bag

import au.com.alfie.ecomm.domain.UseCaseInteractor
import au.com.alfie.ecomm.domain.UseCaseResult
import au.com.alfie.ecomm.domain.doOnResult
import au.com.alfie.ecomm.repository.bag.BagProduct
import au.com.alfie.ecomm.repository.bag.BagRepository
import javax.inject.Inject

class AddToBagUseCase @Inject constructor(
private val bagRepository: BagRepository
) : UseCaseInteractor {

suspend operator fun invoke(productId: String, variantSku: String) =
run(bagRepository.addToBag(BagProduct(productId = productId, variantSku = variantSku))).doOnResult(
onSuccess = { UseCaseResult.Success(it) },
onError = { UseCaseResult.Error(it) }
)
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
package au.com.alfie.ecomm.domain.usecase.bag

import au.com.alfie.ecomm.domain.UseCaseInteractor
import au.com.alfie.ecomm.domain.UseCaseResult
import au.com.alfie.ecomm.repository.bag.BagProduct
import au.com.alfie.ecomm.repository.bag.BagRepository
import kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.flow.map
import javax.inject.Inject

class GetBagUseCase @Inject constructor(
private val bagRepository: BagRepository
) : UseCaseInteractor {

suspend operator fun invoke(): Flow<UseCaseResult<List<BagProduct>>> =
bagRepository.getBag().map { repositoryResult ->
run(repositoryResult)
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
package au.com.alfie.ecomm.domain.usecase.bag

import au.com.alfie.ecomm.domain.UseCaseInteractor
import au.com.alfie.ecomm.domain.UseCaseResult
import au.com.alfie.ecomm.domain.doOnResult
import au.com.alfie.ecomm.repository.bag.BagProduct
import au.com.alfie.ecomm.repository.bag.BagRepository
import javax.inject.Inject

class RemoveFromBagUseCase @Inject constructor(
private val bagRepository: BagRepository
) : UseCaseInteractor {

suspend operator fun invoke(bagProduct: BagProduct) =
run(bagRepository.removeFromBag(bagProduct)).doOnResult(
onSuccess = { UseCaseResult.Success(it) },
onError = { UseCaseResult.Error(it) }
)
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,55 @@
package au.com.alfie.ecomm.domain.bag

import au.com.alfie.ecomm.domain.usecase.bag.AddToBagUseCase
import au.com.alfie.ecomm.repository.bag.BagProduct
import au.com.alfie.ecomm.repository.bag.BagRepository
import au.com.alfie.ecomm.repository.product.ProductRepository
import au.com.alfie.ecomm.repository.result.ErrorResult
import au.com.alfie.ecomm.repository.result.RepositoryResult
import io.mockk.coEvery
import io.mockk.coVerify
import io.mockk.every
import io.mockk.impl.annotations.InjectMockKs
import io.mockk.impl.annotations.RelaxedMockK
import io.mockk.junit5.MockKExtension
import io.mockk.mockk
import kotlinx.coroutines.test.runTest
import org.junit.jupiter.api.Test
import org.junit.jupiter.api.extension.ExtendWith

@ExtendWith(MockKExtension::class)
class AddToBagUseCaseTest {
@RelaxedMockK
private lateinit var bagRepository: BagRepository

@RelaxedMockK
private lateinit var productRepository: ProductRepository

@InjectMockKs
lateinit var subject: AddToBagUseCase

@Test
fun `add to bag, with success result`() = runTest {
val mockProduct = mockk<BagProduct> {
every { productId } returns "10"
every { variantSku } returns "34535"
}

coEvery { bagRepository.addToBag(mockProduct) } returns RepositoryResult.Success(true)
subject(productId = "10", variantSku = "34535")
coVerify { bagRepository.addToBag(mockProduct) }
}

@Test
fun `add to bag, with error result`() = runTest {
val mockProduct = mockk<BagProduct> {
every { productId } returns "10"
every { variantSku } returns "34535"
}
val errorResult = mockk<ErrorResult>()

coEvery { bagRepository.addToBag(mockProduct) } returns RepositoryResult.Error(errorResult)
subject("10", "34535")
coVerify { bagRepository.addToBag(mockProduct) }
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,50 @@
package au.com.alfie.ecomm.domain.bag

import au.com.alfie.ecomm.domain.UseCaseResult
import au.com.alfie.ecomm.domain.usecase.bag.GetBagUseCase
import au.com.alfie.ecomm.repository.bag.BagProduct
import au.com.alfie.ecomm.repository.bag.BagRepository
import au.com.alfie.ecomm.repository.result.ErrorResult
import au.com.alfie.ecomm.repository.result.RepositoryResult
import io.mockk.coEvery
import io.mockk.impl.annotations.InjectMockKs
import io.mockk.impl.annotations.RelaxedMockK
import io.mockk.junit5.MockKExtension
import io.mockk.mockk
import kotlinx.coroutines.flow.first
import kotlinx.coroutines.flow.flowOf
import kotlinx.coroutines.test.runTest
import org.junit.jupiter.api.Test
import org.junit.jupiter.api.extension.ExtendWith
import kotlin.test.assertEquals

@ExtendWith(MockKExtension::class)
class GetBagUseCaseTest {
@RelaxedMockK
private lateinit var bagRepository: BagRepository

@InjectMockKs
lateinit var subject: GetBagUseCase

@Test
fun `get list of products in the bag`() = runTest {
val mockBag = mockk<List<BagProduct>>()
coEvery { bagRepository.getBag() } returns flowOf(RepositoryResult.Success(mockBag))

val expected = UseCaseResult.Success(mockBag)
val result = subject().first()

assertEquals(expected, result)
}

@Test
fun `get list of products in the bag, and returns an error`() = runTest {
val errorResult = mockk<ErrorResult>()
coEvery { bagRepository.getBag() } returns flowOf(RepositoryResult.Error(errorResult))

val expected = UseCaseResult.Error(errorResult)
val result = subject().first()

assertEquals(expected, result)
}
}
Loading