Skip to content

Commit

Permalink
Feature/mark unmark catalog detail favorite (#45)
Browse files Browse the repository at this point in the history
* feat(mobile-app): Updated javadoc
* feat(core-database): Updated catalog favorites dao and local datasource, adding query for check if catalog item exists as favorite
* feat(core-database): Updated catalog favorites dao and local datasource
* feat(core-database): Update catalog favorite data insert function
* feat(mobile-app): Update unit tests for catalog detail viewmodel and repository
* feat(mobile-app): Add favorite state change for catalog detail data
* feat(mobile-app): Update catalog detail screen layouts
* feat(mobile-app): Update catalog detail screen slots
* feat(mobile-app): Add catalog detail item favorite state change viewmodel function
  • Loading branch information
marlonlom authored Mar 30, 2024
1 parent 3fae778 commit 52191cf
Show file tree
Hide file tree
Showing 18 changed files with 323 additions and 47 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@
package dev.marlonlom.apps.cappajv.features.catalog_detail

import dev.marlonlom.apps.cappajv.core.database.datasource.LocalDataSource
import dev.marlonlom.apps.cappajv.core.database.entities.CatalogFavoriteItem
import dev.marlonlom.apps.cappajv.core.database.entities.CatalogItem
import dev.marlonlom.apps.cappajv.core.database.entities.CatalogPunctuation
import kotlinx.coroutines.CoroutineDispatcher
Expand All @@ -16,17 +17,23 @@ import kotlinx.coroutines.flow.combine
/**
* Data class definition for catalog item detail.
*
* @property product product detail
* @property points product points list
* @author marlonlom
*
* @property product Catalog product detail
* @property isFavorite True/False if catalog product is marked as favorite.
* @property points Catalog product points list
*/
data class CatalogDetail(
val product: CatalogItem,
val isFavorite: Boolean,
val points: List<CatalogPunctuation>
)

/**
* Catalog details repository class.
*
* @author marlonlom
*
* @property localDataSource local data source dependency
* @property coroutineDispatcher coroutine dispatcher
*/
Expand All @@ -39,14 +46,19 @@ class CatalogDetailRepository(
coroutineDispatcher.run {
return combine(
localDataSource.findProduct(itemId),
localDataSource.isFavorite(itemId),
localDataSource.getPunctuations(itemId)
) { product, points ->
) { product, isFavorite, points ->
try {
return@combine product?.let {
if (product.id == -1L) {
null
} else {
CatalogDetail(product, points)
CatalogDetail(
product = product,
isFavorite = isFavorite > 0,
points = points
)
}
}
} catch (e: Exception) {
Expand All @@ -56,4 +68,18 @@ class CatalogDetailRepository(
}
}

/**
* Inserts a catalog item marked as favorite.
*
* @param favoriteItem Catalog favorite item to be saved.
*/
suspend fun saveFavorite(favoriteItem: CatalogFavoriteItem) = localDataSource.insertFavoriteProduct(favoriteItem)

/**
* Deletes a catalog item marked as favorite, using its provided id.
*
* @param catalogId Catalog item id.
*/
suspend fun deleteFavorite(catalogId: Long) = localDataSource.deleteFavorite(catalogId)

}
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,8 @@ import org.koin.androidx.compose.koinViewModel
* @author marlonlom
*
* @param appState Application ui state.
* @param appContentCallbacks Application content callbacks.
* @param isRouting True/False if should navigate through routing.
* @param catalogId Selected catalog item id.
* @param viewModel Catalog detail viewmodel.
*/
Expand All @@ -48,7 +50,8 @@ fun CatalogDetailRoute(
CatalogDetailRouteScreen(
appState = appState,
appContentCallbacks = appContentCallbacks,
isRouting = isRouting,
detailUiState = detailUiState,
isRouting = isRouting,
onCatalogItemFavoriteChanged = viewModel::toggleFavorite,
)
}
Original file line number Diff line number Diff line change
Expand Up @@ -8,13 +8,16 @@ package dev.marlonlom.apps.cappajv.features.catalog_detail
import androidx.lifecycle.SavedStateHandle
import androidx.lifecycle.ViewModel
import androidx.lifecycle.viewModelScope
import dev.marlonlom.apps.cappajv.core.database.entities.CatalogFavoriteItem
import dev.marlonlom.apps.cappajv.core.database.entities.CatalogItem
import dev.marlonlom.apps.cappajv.features.catalog_detail.CatalogDetailUiState.Found
import dev.marlonlom.apps.cappajv.features.catalog_detail.CatalogDetailUiState.NotFound
import kotlinx.coroutines.ExperimentalCoroutinesApi
import kotlinx.coroutines.flow.SharingStarted
import kotlinx.coroutines.flow.flatMapLatest
import kotlinx.coroutines.flow.mapLatest
import kotlinx.coroutines.flow.stateIn
import kotlinx.coroutines.launch

/**
* Catalog detail view model class.
Expand Down Expand Up @@ -51,10 +54,41 @@ class CatalogDetailViewModel(
savedStateHandle[CATALOG_DETAIL_ID_KEY] = itemId
}

/**
* Handles favorite state for catalog detail item.
*
* @param product Catalog detail item.
* @param isFavorite True/False for catalog detail item to be marked as favorite.
*/
fun toggleFavorite(
product: CatalogItem,
isFavorite: Boolean
) {
viewModelScope.launch {
if (isFavorite) {
val favoriteItem = product.let {
CatalogFavoriteItem(
it.id,
it.title,
it.picture,
it.category,
it.samplePunctuation,
it.punctuationsCount
)
}
repository.saveFavorite(favoriteItem)
} else {
repository.deleteFavorite(product.id)
}
}
}

companion object {

/** Constant for default catalog item id. */
private const val NO_CATALOG_ID = 0L

/** Constant for default catalog item id key. */
private const val CATALOG_DETAIL_ID_KEY = "CATALOG_DETAIL_ID"

}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ import androidx.compose.foundation.layout.Spacer
import androidx.compose.foundation.layout.padding
import androidx.compose.foundation.layout.width
import androidx.compose.material.icons.Icons
import androidx.compose.material.icons.rounded.Favorite
import androidx.compose.material.icons.rounded.FavoriteBorder
import androidx.compose.material.icons.rounded.Share
import androidx.compose.material3.Icon
Expand All @@ -35,13 +36,17 @@ import dev.marlonlom.apps.cappajv.ui.main.CappajvAppState
*
* @param appState Application ui state.
* @param appContentCallbacks Application content callbacks.
* @param product Catalog detailed information
* @param product Catalog detailed information.
* @param isFavorite True/False if catalog detail item is favorite.
* @param onCatalogItemFavoriteChanged Action for catalog detail item favorite state changed.
*/
@Composable
fun CatalogDetailButtonsBar(
appState: CappajvAppState,
appContentCallbacks: AppContentCallbacks,
product: CatalogItem,
isFavorite: Boolean,
onCatalogItemFavoriteChanged: (CatalogItem, Boolean) -> Unit,
) {
Row(
modifier = Modifier
Expand All @@ -50,10 +55,13 @@ fun CatalogDetailButtonsBar(
verticalAlignment = Alignment.CenterVertically,
) {
OutlinedButton(
onClick = { /*TODO*/ },
onClick = { onCatalogItemFavoriteChanged(product, !isFavorite) },
shape = MaterialTheme.shapes.small,
) {
Icon(imageVector = Icons.Rounded.FavoriteBorder, contentDescription = null)
Icon(
imageVector = if (isFavorite) Icons.Rounded.Favorite else Icons.Rounded.FavoriteBorder,
contentDescription = null
)
Spacer(modifier = Modifier.width(10.dp))
Text(text = stringResource(id = R.string.text_catalog_detail_button_favorite))
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -7,22 +7,36 @@ package dev.marlonlom.apps.cappajv.features.catalog_detail.screens

import androidx.compose.foundation.ExperimentalFoundationApi
import androidx.compose.runtime.Composable
import dev.marlonlom.apps.cappajv.core.database.entities.CatalogItem
import dev.marlonlom.apps.cappajv.features.catalog_detail.CatalogDetailUiState
import dev.marlonlom.apps.cappajv.ui.main.AppContentCallbacks
import dev.marlonlom.apps.cappajv.ui.main.CappajvAppState

/**
* Catalog detail route screen content composable ui.
*
* @author marlonlom
*
* @param appState Application ui state.
* @param appContentCallbacks Application content callbacks.
* @param detailUiState Catalog detail ui state.
* @param isRouting True/False if should navigate through routing.
* @param onCatalogItemFavoriteChanged Action for catalog detail item favorite state changed.
*/
@ExperimentalFoundationApi
@Composable
fun CatalogDetailRouteScreen(
appState: CappajvAppState,
appContentCallbacks: AppContentCallbacks,
detailUiState: CatalogDetailUiState,
isRouting: Boolean,
detailUiState: CatalogDetailUiState
onCatalogItemFavoriteChanged: (CatalogItem, Boolean) -> Unit,
) = when {
else -> DefaultPortraitCatalogDetailScreen(
appState = appState,
appContentCallbacks = appContentCallbacks,
detailUiState = detailUiState,
isRouting = isRouting
isRouting = isRouting,
onCatalogItemFavoriteChanged = onCatalogItemFavoriteChanged
)
}
Original file line number Diff line number Diff line change
Expand Up @@ -17,20 +17,30 @@ import androidx.compose.runtime.Composable
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.unit.dp
import dev.marlonlom.apps.cappajv.core.database.entities.CatalogItem
import dev.marlonlom.apps.cappajv.features.catalog_detail.CatalogDetailUiState
import dev.marlonlom.apps.cappajv.features.catalog_detail.parts.CatalogDetailTopBar
import dev.marlonlom.apps.cappajv.features.catalog_detail.slots.CatalogDetailResultsSlot
import dev.marlonlom.apps.cappajv.ui.main.AppContentCallbacks
import dev.marlonlom.apps.cappajv.ui.main.CappajvAppState


/**
* @author marlonlom
*
* @param appState Application ui state.
* @param appContentCallbacks Application content callbacks.
* @param detailUiState Catalog detail ui state.
* @param isRouting True/False if should navigate through routing.
* @param onCatalogItemFavoriteChanged Action for catalog detail item favorite state changed.
*/
@ExperimentalFoundationApi
@Composable
fun DefaultPortraitCatalogDetailScreen(
appState: CappajvAppState,
appContentCallbacks: AppContentCallbacks,
detailUiState: CatalogDetailUiState,
isRouting: Boolean,
onCatalogItemFavoriteChanged: (CatalogItem, Boolean) -> Unit,
) {
val contentHorizontalPadding = when {
appState.isLandscape.not().and(appState.isMediumWidth) -> 40.dp
Expand All @@ -46,6 +56,11 @@ fun DefaultPortraitCatalogDetailScreen(
horizontalAlignment = Alignment.CenterHorizontally,
) {
CatalogDetailTopBar(appState, isRouting)
CatalogDetailResultsSlot(appState, appContentCallbacks, detailUiState)
CatalogDetailResultsSlot(
appState = appState,
appContentCallbacks = appContentCallbacks,
detailUiState = detailUiState,
onCatalogItemFavoriteChanged = onCatalogItemFavoriteChanged
)
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -8,19 +8,36 @@ package dev.marlonlom.apps.cappajv.features.catalog_detail.slots
import androidx.compose.foundation.ExperimentalFoundationApi
import androidx.compose.material3.Text
import androidx.compose.runtime.Composable
import dev.marlonlom.apps.cappajv.core.database.entities.CatalogItem
import dev.marlonlom.apps.cappajv.features.catalog_detail.CatalogDetailUiState
import dev.marlonlom.apps.cappajv.ui.main.AppContentCallbacks
import dev.marlonlom.apps.cappajv.ui.main.CappajvAppState

/**
* Catalog detail results slot composable ui.
*
* @author marlonlom
*
* @param appState Application ui state.
* @param appContentCallbacks Application content callbacks.
* @param detailUiState Catalog detail ui state.
* @param onCatalogItemFavoriteChanged Action for catalog detail item favorite state changed.
*/
@ExperimentalFoundationApi
@Composable
fun CatalogDetailResultsSlot(
appState: CappajvAppState,
appContentCallbacks: AppContentCallbacks,
detailUiState: CatalogDetailUiState,
onCatalogItemFavoriteChanged: (CatalogItem, Boolean) -> Unit,
) = when (detailUiState) {
is CatalogDetailUiState.Found -> {
FoundCatalogDetailSlot(appState, appContentCallbacks, detailUiState)
FoundCatalogDetailSlot(
appState = appState,
appContentCallbacks = appContentCallbacks,
detailUiState = detailUiState,
onCatalogItemFavoriteChanged = onCatalogItemFavoriteChanged
)
}

CatalogDetailUiState.NotFound -> Text("Select from list for view its detailed information.")
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -25,12 +25,16 @@ import dev.marlonlom.apps.cappajv.ui.main.CappajvAppState
* @param appState Application ui state.
* @param appContentCallbacks Application content callbacks.
* @param product Catalog detailed information.
* @param isFavorite True/False if catalog detail item is favorite.
* @param onCatalogItemFavoriteChanged Action for catalog detail item favorite state changed.
*/
@Composable
fun FoundCatalogDetailHeadingSlot(
appState: CappajvAppState,
appContentCallbacks: AppContentCallbacks,
product: CatalogItem,
isFavorite: Boolean,
onCatalogItemFavoriteChanged: (CatalogItem, Boolean) -> Unit,
) = Column(
horizontalAlignment = Alignment.CenterHorizontally,
) {
Expand All @@ -42,8 +46,10 @@ fun FoundCatalogDetailHeadingSlot(
CatalogDetailProductCategoryText(product)
CatalogDetailButtonsBar(
appState = appState,
product = product,
appContentCallbacks = appContentCallbacks,
product = product,
isFavorite = isFavorite,
onCatalogItemFavoriteChanged = onCatalogItemFavoriteChanged,
)
CatalogDetailProductDescriptionText(product)
}
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ package dev.marlonlom.apps.cappajv.features.catalog_detail.slots

import androidx.compose.foundation.ExperimentalFoundationApi
import androidx.compose.runtime.Composable
import dev.marlonlom.apps.cappajv.core.database.entities.CatalogItem
import dev.marlonlom.apps.cappajv.features.catalog_detail.CatalogDetailUiState
import dev.marlonlom.apps.cappajv.ui.main.AppContentCallbacks
import dev.marlonlom.apps.cappajv.ui.main.CappajvAppState
Expand All @@ -17,16 +18,25 @@ import dev.marlonlom.apps.cappajv.ui.main.CappajvAppState
* @author marlonlom
*
* @param appState Application ui state.
* @param appContentCallbacks Application content callbacks.
* @param detailUiState Found catalog item information.
* @param onCatalogItemFavoriteChanged Action for catalog detail item favorite state changed.
*/
@ExperimentalFoundationApi
@Composable
fun FoundCatalogDetailSlot(
appState: CappajvAppState,
appContentCallbacks: AppContentCallbacks,
detailUiState: CatalogDetailUiState.Found,
onCatalogItemFavoriteChanged: (CatalogItem, Boolean) -> Unit,
) {
val (product, points) = detailUiState.detail
FoundCatalogDetailHeadingSlot(appState, appContentCallbacks, product)
val (product, isFavorite, points) = detailUiState.detail
FoundCatalogDetailHeadingSlot(
appState = appState,
appContentCallbacks = appContentCallbacks,
product = product,
isFavorite = isFavorite,
onCatalogItemFavoriteChanged = onCatalogItemFavoriteChanged
)
FoundCatalogDetailPointsSlot(appState, points)
}
Loading

0 comments on commit 52191cf

Please sign in to comment.