Skip to content

Commit

Permalink
add methods for retrieving, adding, and deleting recipe ingredients
Browse files Browse the repository at this point in the history
  • Loading branch information
Cody Weaver authored and Cody Weaver committed Jan 23, 2024
1 parent 814f2a2 commit 9ff701f
Show file tree
Hide file tree
Showing 16 changed files with 226 additions and 129 deletions.
2 changes: 1 addition & 1 deletion app/src/main/AndroidManifest.xml
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@
android:supportsRtl="true"
android:theme="@android:style/Theme.DeviceDefault.NoActionBar"
tools:targetApi="34"
android:usesCleartextTraffic="true"> <!-- ONLY FOR EMULATOR USE -->
android:usesCleartextTraffic="false"> <!-- true ONLY FOR EMULATOR USE -->
<activity
android:name=".MainActivity"
android:exported="true">
Expand Down
2 changes: 1 addition & 1 deletion app/src/main/java/com/android/pocketalchemy/PaNavHost.kt
Original file line number Diff line number Diff line change
Expand Up @@ -67,7 +67,7 @@ fun PaNavHost(
val recipeIdArg = remember { backStackEntry.arguments?.getString("recipeId") }

LaunchedEffect(recipeIdArg) {
editRecipeViewModel.setRecipeId(recipeIdArg)
editRecipeViewModel.setRecipe(recipeIdArg)
}

EditRecipeScreen(
Expand Down
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
package com.android.pocketalchemy.editrecipe

import android.util.Log
import androidx.compose.foundation.layout.Box
import androidx.compose.foundation.layout.Column
import androidx.compose.foundation.layout.Row
Expand All @@ -15,6 +16,7 @@ import androidx.compose.material3.Scaffold
import androidx.compose.material3.Text
import androidx.compose.runtime.Composable
import androidx.compose.runtime.State
import androidx.compose.runtime.collectAsState
import androidx.compose.ui.Modifier
import androidx.compose.ui.res.stringResource
import androidx.compose.ui.unit.dp
Expand All @@ -40,8 +42,8 @@ fun EditRecipeScreen(
editRecipeViewModel: EditRecipeViewModel
) {
val appBarTitle = R.string.edit_recipe_title
val recipeState: State<Recipe> = editRecipeViewModel.recipeState
val recipe = recipeState.value
val editRecipeUiState: State<EditRecipeUiState>
= editRecipeViewModel.editRecipeUiState.collectAsState()

Scaffold(
contentColor = MaterialTheme.colorScheme.onBackground,
Expand All @@ -56,27 +58,32 @@ fun EditRecipeScreen(
Column(
modifier = Modifier.padding(scaffoldPadding)
) {
val recipe = editRecipeUiState.value.recipe

TitleField(
recipe = recipe,
onUpdate = {
editRecipeViewModel.updateRecipeState(
recipe.copy(title = it)
editRecipeViewModel.updateUiState(
recipe = recipe.copy(title = it)
)
}
)

DescriptionField(
recipe = recipe,
onUpdate = {
editRecipeViewModel.updateRecipeState(
recipe.copy(description = it)
editRecipeViewModel.updateUiState(
recipe = recipe.copy(description = it)
)
}
)

Row( // Ingredients

) { /* TODO: */ }
) { /* TODO: */
val ingredients = editRecipeUiState.value.ingredients
Log.d(TAG, ingredients.toString())
}

Row( // Recipe Instructions

Expand Down Expand Up @@ -106,8 +113,6 @@ fun EditRecipeScreen(

/**
* Draws title field and requests updates from view model.
* @param recipe
* @param onUpdate
*/
@Composable
private fun TitleField(
Expand Down Expand Up @@ -137,8 +142,6 @@ private fun TitleField(

/**
* Draws description field and requests updates from view model.
* @param recipe
* @param onUpdate
*/
@Composable
private fun DescriptionField(
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
package com.android.pocketalchemy.editrecipe

import com.android.pocketalchemy.model.Recipe
import com.android.pocketalchemy.model.RecipeIngredient

/**
* Wrapper class for holding state of EditRecipeScreen
*/
data class EditRecipeUiState(
var recipe: Recipe,
val ingredients: List<RecipeIngredient>
)
Original file line number Diff line number Diff line change
@@ -1,18 +1,22 @@
package com.android.pocketalchemy.editrecipe

import android.util.Log
import androidx.compose.runtime.MutableState
import androidx.compose.runtime.State
import androidx.compose.runtime.mutableStateOf
import androidx.lifecycle.SavedStateHandle
import androidx.lifecycle.ViewModel
import androidx.lifecycle.viewModelScope
import com.android.pocketalchemy.model.Recipe
import com.android.pocketalchemy.model.RecipeIngredient
import com.android.pocketalchemy.repository.AuthRepository
import com.android.pocketalchemy.repository.RecipeIngredientRepository
import com.android.pocketalchemy.repository.RecipeRepository
import com.google.firebase.firestore.toObject
import dagger.hilt.android.lifecycle.HiltViewModel
import kotlinx.coroutines.CoroutineDispatcher
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.flow.MutableStateFlow
import kotlinx.coroutines.flow.StateFlow
import kotlinx.coroutines.flow.asStateFlow
import kotlinx.coroutines.flow.update
import kotlinx.coroutines.launch
import javax.inject.Inject

Expand All @@ -23,8 +27,10 @@ import javax.inject.Inject
class EditRecipeViewModel @Inject constructor(
private val savedStateHandle: SavedStateHandle,
private val recipeRepository: RecipeRepository,
private val authRepository: AuthRepository
private val recipeIngredientRepository: RecipeIngredientRepository,
private val authRepository: AuthRepository,
) : ViewModel() {
private val ioDispatcher: CoroutineDispatcher = Dispatchers.IO
/**
* Retrieves recipe id from SavedStateHandle
*/
Expand All @@ -37,67 +43,99 @@ class EditRecipeViewModel @Inject constructor(
private val userId: String
get() = authRepository.getUserIdString() ?: ""

/** ui state model for EditRecipeScreen */
private var _editRecipeUiState: MutableStateFlow<EditRecipeUiState>
= MutableStateFlow(EditRecipeUiState(
Recipe(id = recipeId, userId = userId), emptyList())
)

/** public accessor for ui state model */
val editRecipeUiState: StateFlow<EditRecipeUiState>
get() = _editRecipeUiState.asStateFlow()

/**
* ui state model for EditRecipeScreen
*/
private var _recipeState: MutableState<Recipe>
= mutableStateOf(Recipe(userId = userId, id = recipeId))
/**
* public accessor for ui state model
* List of ingredients to be deleted from firestore on save.
* Ingredients that are added and then removed before a
* call to [saveRecipe] are not tracked as there is no
* need to delete them from firestore.
*/
val recipeState: State<Recipe>
get() = _recipeState
private val recipeIngredientsToDelete = mutableListOf<RecipeIngredient>()

/**
* Initializes view model with recipe from recipeId.
* Has no effect if already set.
* @param recipeId used to retrieve recipe document
*/
fun setRecipeId(recipeId: String?) {
// no effect if already set
if (this.recipeId == null) {
viewModelScope.launch(
Dispatchers.IO
) {
val recipeDoc = recipeRepository.getRecipe(recipeId)
savedStateHandle[EDIT_RECIPE_ID_KEY] = recipeDoc.id
getRecipeSnapshot()
fun setRecipe(recipeId: String?) {
viewModelScope.launch(
ioDispatcher
) {
val recipeDoc = recipeRepository.getRecipe(recipeId)
savedStateHandle[EDIT_RECIPE_ID_KEY] = recipeDoc.id

recipeDoc.get()
.addOnSuccessListener { snapshot ->
snapshot.toObject<Recipe>()?.let { recipeSnapshot ->
updateUiState(recipe = recipeSnapshot)
}
}
.addOnFailureListener {
Log.w(TAG, "Cannot get recipe with id: $recipeId, ex: $it")
}

// sets initial ingredient list
recipeIngredientRepository.getRecipeIngredients(recipeId.toString()) {
updateUiState(recipeIngredients = it)
}
}
}

/**
* Resets recipeId used by view model
*/
fun clearRecipeId() {
savedStateHandle[EDIT_RECIPE_ID_KEY] = null
}

/**
* Sends request to get recipe document and
* adds listener to update _recipe with document
* content when request completes.
* updates EditRecipeUiState
*/
private fun getRecipeSnapshot() {
val recipeDoc = recipeRepository.getRecipe(recipeId)

recipeDoc.get()
.addOnSuccessListener { snapshot ->
snapshot.toObject<Recipe>()?.let {
updateRecipeState(it)
fun updateUiState(
recipe: Recipe? = null,
recipeIngredients: List<RecipeIngredient>? = null
) {
viewModelScope.launch {
recipe?.let { newRecipe ->
_editRecipeUiState.update {
it.copy(recipe = newRecipe)
}
}
.addOnFailureListener {
Log.w(TAG, "Cannot get recipe with id: $recipeId, ex: $it")

recipeIngredients?.let { newRecipeIngredients ->
_editRecipeUiState.update {
it.copy(ingredients = newRecipeIngredients)
}
}
}
}

/**
* Updates ui state model
* Resets recipeId used by view model
*/
fun updateRecipeState(recipe: Recipe) {
fun clearRecipeId() {
savedStateHandle[EDIT_RECIPE_ID_KEY] = null
}

fun addRecipeIngredient(recipeIngredient: RecipeIngredient) {
viewModelScope.launch {
val recipeIngredients = _editRecipeUiState.value.ingredients
updateUiState(
recipeIngredients = recipeIngredients + recipeIngredient
)
}
}

fun removeRecipeIngredient(recipeIngredient: RecipeIngredient) {
viewModelScope.launch {
_recipeState.value = recipe.copy(id = recipeId)
val recipeIngredients = _editRecipeUiState.value.ingredients
updateUiState(
recipeIngredients = recipeIngredients.filter {
it.ingredientId != recipeIngredient.id
}
)
}
}

Expand All @@ -107,9 +145,9 @@ class EditRecipeViewModel @Inject constructor(
fun saveRecipe() {
// TODO: Check no required fields are empty!!!
viewModelScope.launch(
Dispatchers.IO
ioDispatcher
) {
recipeRepository.insertRecipe(recipeState.value)
recipeRepository.setRecipe(_editRecipeUiState.value.recipe)
clearRecipeId()
}
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -6,4 +6,5 @@ package com.android.pocketalchemy.firebase
object FirestoreCollections {
const val RECIPE_COLLECTION = "recipes"
const val INGREDIENT_COLLECTION = "ingredients"
const val RECIPE_INGREDIENTS_COLLECTION = "recipe-ingredients"
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,44 @@
package com.android.pocketalchemy.model

import com.google.errorprone.annotations.Keep
import com.google.firebase.firestore.DocumentId
import com.google.firebase.firestore.IgnoreExtraProperties

/**
* A lightweight representation of a
* an ingredient in a recipe.
* @param ingredientId
* @param description
* @param gramWeight
*/
@Keep
@IgnoreExtraProperties
data class RecipeIngredient(
@DocumentId val id: String? = null,
val recipeId: String = "",
val ingredientId: String = "",
val description: String = "",
val gramWeight: Float = 0f
) {
/**
* Shortened recipe description for recipe view
*/
val name: String
get() {
val splitDescription = description.split(", ")
val nameWords = mutableListOf<String>()
for (i in 0..<4) {
nameWords.add(splitDescription[i])
}
if (splitDescription.size > 4) {
nameWords.add("...")
}
return nameWords.joinToString(" ")
}

companion object {
/** Field Names */
const val RECIPE_ID_KEY = "recipeId"
const val INGREDIENT_ID_KEY = "ingredientId"
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -15,15 +15,9 @@ import javax.inject.Inject
class RecipeListViewModel @Inject constructor(
private val recipeRepository: RecipeRepository,
) : ViewModel() {

/**
* Retrieves a flow of current users recipes
*/
val recipes: Flow<List<Recipe>>
get() = recipeRepository.getUserRecipeList()

}




}
Loading

0 comments on commit 9ff701f

Please sign in to comment.