Use intermediate representation for AddRecipe draft

This commit is contained in:
Kirill Kamakin
2022-08-04 21:19:15 +02:00
parent 8784912cdb
commit 057651c60f
14 changed files with 154 additions and 119 deletions

View File

@@ -1,13 +0,0 @@
package gq.kirmanak.mealient.data.add
import gq.kirmanak.mealient.data.add.models.AddRecipeRequest
import kotlinx.coroutines.flow.Flow
interface AddRecipeStorage {
val updates: Flow<AddRecipeRequest>
suspend fun save(addRecipeRequest: AddRecipeRequest)
suspend fun clear()
}

View File

@@ -2,10 +2,15 @@ package gq.kirmanak.mealient.data.add.impl
import gq.kirmanak.mealient.data.add.AddRecipeDataSource
import gq.kirmanak.mealient.data.add.AddRecipeRepo
import gq.kirmanak.mealient.data.add.AddRecipeStorage
import gq.kirmanak.mealient.data.add.models.AddRecipeIngredient
import gq.kirmanak.mealient.data.add.models.AddRecipeInstruction
import gq.kirmanak.mealient.data.add.models.AddRecipeRequest
import gq.kirmanak.mealient.data.add.models.AddRecipeSettings
import gq.kirmanak.mealient.datastore.recipe.AddRecipeDraft
import gq.kirmanak.mealient.datastore.recipe.AddRecipeStorage
import kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.flow.first
import kotlinx.coroutines.flow.map
import timber.log.Timber
import javax.inject.Inject
import javax.inject.Singleton
@@ -17,11 +22,32 @@ class AddRecipeRepoImpl @Inject constructor(
) : AddRecipeRepo {
override val addRecipeRequestFlow: Flow<AddRecipeRequest>
get() = addRecipeStorage.updates
get() = addRecipeStorage.updates.map { it ->
AddRecipeRequest(
name = it.recipeName,
description = it.recipeDescription,
recipeYield = it.recipeYield,
recipeIngredient = it.recipeIngredients.map { AddRecipeIngredient(note = it) },
recipeInstructions = it.recipeInstructions.map { AddRecipeInstruction(text = it) },
settings = AddRecipeSettings(
public = it.isRecipePublic,
disableComments = it.areCommentsDisabled,
)
)
}
override suspend fun preserve(recipe: AddRecipeRequest) {
Timber.v("preserveRecipe() called with: recipe = $recipe")
addRecipeStorage.save(recipe)
val input = AddRecipeDraft(
recipeName = recipe.name,
recipeDescription = recipe.description,
recipeYield = recipe.recipeYield,
recipeInstructions = recipe.recipeInstructions.map { it.text },
recipeIngredients = recipe.recipeIngredient.map { it.note },
isRecipePublic = recipe.settings.public,
areCommentsDisabled = recipe.settings.disableComments,
)
addRecipeStorage.save(input)
}
override suspend fun clear() {

View File

@@ -1,30 +0,0 @@
package gq.kirmanak.mealient.data.add.impl
import androidx.datastore.core.DataStore
import gq.kirmanak.mealient.data.add.AddRecipeStorage
import gq.kirmanak.mealient.data.add.models.AddRecipeRequest
import gq.kirmanak.mealient.datastore.recipe.AddRecipeInput
import kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.flow.map
import timber.log.Timber
import javax.inject.Inject
import javax.inject.Singleton
@Singleton
class AddRecipeStorageImpl @Inject constructor(
private val dataStore: DataStore<AddRecipeInput>,
) : AddRecipeStorage {
override val updates: Flow<AddRecipeRequest>
get() = dataStore.data.map { AddRecipeRequest(it) }
override suspend fun save(addRecipeRequest: AddRecipeRequest) {
Timber.v("saveRecipeInput() called with: addRecipeRequest = $addRecipeRequest")
dataStore.updateData { addRecipeRequest.toInput() }
}
override suspend fun clear() {
Timber.v("clearRecipeInput() called")
dataStore.updateData { AddRecipeInput.getDefaultInstance() }
}
}

View File

@@ -1,6 +1,6 @@
package gq.kirmanak.mealient.data.add.models
import gq.kirmanak.mealient.datastore.recipe.AddRecipeInput
import gq.kirmanak.mealient.datastore.recipe.AddRecipeDraft
import kotlinx.serialization.SerialName
import kotlinx.serialization.Serializable
@@ -21,27 +21,27 @@ data class AddRecipeRequest(
@SerialName("assets") val assets: List<String> = emptyList(),
@SerialName("settings") val settings: AddRecipeSettings = AddRecipeSettings(),
) {
constructor(input: AddRecipeInput) : this(
constructor(input: AddRecipeDraft) : this(
name = input.recipeName,
description = input.recipeDescription,
recipeYield = input.recipeYield,
recipeIngredient = input.recipeIngredientsList.map { AddRecipeIngredient(note = it) },
recipeInstructions = input.recipeInstructionsList.map { AddRecipeInstruction(text = it) },
recipeIngredient = input.recipeIngredients.map { AddRecipeIngredient(note = it) },
recipeInstructions = input.recipeInstructions.map { AddRecipeInstruction(text = it) },
settings = AddRecipeSettings(
public = input.isRecipePublic,
disableComments = input.areCommentsDisabled,
)
)
fun toInput(): AddRecipeInput = AddRecipeInput.newBuilder()
.setRecipeName(name)
.setRecipeDescription(description)
.setRecipeYield(recipeYield)
.setIsRecipePublic(settings.public)
.setAreCommentsDisabled(settings.disableComments)
.addAllRecipeIngredients(recipeIngredient.map { it.note })
.addAllRecipeInstructions(recipeInstructions.map { it.text })
.build()
fun toDraft(): AddRecipeDraft = AddRecipeDraft(
recipeName = name,
recipeDescription = description,
recipeYield = recipeYield,
recipeInstructions = recipeInstructions.map { it.text },
recipeIngredients = recipeIngredient.map { it.note },
isRecipePublic = settings.public,
areCommentsDisabled = settings.disableComments,
)
}
@Serializable

View File

@@ -4,7 +4,7 @@ import android.content.SharedPreferences
import androidx.annotation.VisibleForTesting
import androidx.core.content.edit
import gq.kirmanak.mealient.data.auth.AuthStorage
import gq.kirmanak.mealient.di.AuthModule.Companion.ENCRYPTED
import gq.kirmanak.mealient.datastore.DataStoreModule.Companion.ENCRYPTED
import gq.kirmanak.mealient.extensions.prefsChangeFlow
import kotlinx.coroutines.asCoroutineDispatcher
import kotlinx.coroutines.flow.Flow

View File

@@ -7,15 +7,15 @@ import dagger.hilt.InstallIn
import dagger.hilt.components.SingletonComponent
import gq.kirmanak.mealient.data.add.AddRecipeDataSource
import gq.kirmanak.mealient.data.add.AddRecipeRepo
import gq.kirmanak.mealient.data.add.AddRecipeStorage
import gq.kirmanak.mealient.data.add.impl.AddRecipeDataSourceImpl
import gq.kirmanak.mealient.data.add.impl.AddRecipeRepoImpl
import gq.kirmanak.mealient.data.add.impl.AddRecipeService
import gq.kirmanak.mealient.data.add.impl.AddRecipeStorageImpl
import gq.kirmanak.mealient.data.baseurl.BaseURLStorage
import gq.kirmanak.mealient.data.network.RetrofitBuilder
import gq.kirmanak.mealient.data.network.ServiceFactory
import gq.kirmanak.mealient.data.network.createServiceFactory
import gq.kirmanak.mealient.datastore.recipe.AddRecipeStorage
import gq.kirmanak.mealient.datastore.recipe.AddRecipeStorageImpl
import kotlinx.serialization.json.Json
import okhttp3.OkHttpClient
import javax.inject.Named

View File

@@ -2,9 +2,6 @@ package gq.kirmanak.mealient.di
import android.accounts.AccountManager
import android.content.Context
import android.content.SharedPreferences
import androidx.security.crypto.EncryptedSharedPreferences
import androidx.security.crypto.MasterKeys
import dagger.Binds
import dagger.Module
import dagger.Provides
@@ -32,7 +29,6 @@ import javax.inject.Singleton
interface AuthModule {
companion object {
const val ENCRYPTED = "encrypted"
@Provides
@Singleton
@@ -49,23 +45,6 @@ interface AuthModule {
fun provideAccountManager(@ApplicationContext context: Context): AccountManager {
return AccountManager.get(context)
}
@Provides
@Singleton
@Named(ENCRYPTED)
fun provideEncryptedSharedPreferences(
@ApplicationContext applicationContext: Context,
): SharedPreferences {
val keyGenParameterSpec = MasterKeys.AES256_GCM_SPEC
val mainKeyAlias = MasterKeys.getOrCreate(keyGenParameterSpec)
return EncryptedSharedPreferences.create(
ENCRYPTED,
mainKeyAlias,
applicationContext,
EncryptedSharedPreferences.PrefKeyEncryptionScheme.AES256_SIV,
EncryptedSharedPreferences.PrefValueEncryptionScheme.AES256_GCM
)
}
}
@Binds

View File

@@ -1,14 +0,0 @@
syntax = "proto3";
option java_package = "gq.kirmanak.mealient.data.add.models";
option java_multiple_files = true;
message AddRecipeInput {
string recipeName = 1;
string recipeDescription = 2;
string recipeYield = 3;
repeated string recipeInstructions = 4;
repeated string recipeIngredients = 5;
bool isRecipePublic = 6;
bool areCommentsDisabled = 7;
}

View File

@@ -1,21 +1,22 @@
package gq.kirmanak.mealient.data.add.models
import com.google.common.truth.Truth.assertThat
import gq.kirmanak.mealient.datastore.recipe.AddRecipeDraft
import org.junit.Test
class AddRecipeRequestTest {
@Test
fun `when construct from input then fills fields correctly`() {
val input = AddRecipeInput.newBuilder()
.setRecipeName("Recipe name")
.setRecipeDescription("Recipe description")
.setRecipeYield("Recipe yield")
.addAllRecipeIngredients(listOf("Recipe ingredient 1", "Recipe ingredient 2"))
.addAllRecipeInstructions(listOf("Recipe instruction 1", "Recipe instruction 2"))
.setIsRecipePublic(false)
.setAreCommentsDisabled(true)
.build()
val input = AddRecipeDraft(
recipeName = "Recipe name",
recipeDescription = "Recipe description",
recipeYield = "Recipe yield",
recipeInstructions = listOf("Recipe instruction 1", "Recipe instruction 2"),
recipeIngredients = listOf("Recipe ingredient 1", "Recipe ingredient 2"),
isRecipePublic = false,
areCommentsDisabled = true,
)
val expected = AddRecipeRequest(
name = "Recipe name",
@@ -58,16 +59,16 @@ class AddRecipeRequestTest {
)
)
val expected = AddRecipeInput.newBuilder()
.setRecipeName("Recipe name")
.setRecipeDescription("Recipe description")
.setRecipeYield("Recipe yield")
.addAllRecipeIngredients(listOf("Recipe ingredient 1", "Recipe ingredient 2"))
.addAllRecipeInstructions(listOf("Recipe instruction 1", "Recipe instruction 2"))
.setIsRecipePublic(false)
.setAreCommentsDisabled(true)
.build()
val expected = AddRecipeDraft(
recipeName = "Recipe name",
recipeDescription = "Recipe description",
recipeYield = "Recipe yield",
recipeInstructions = listOf("Recipe instruction 1", "Recipe instruction 2"),
recipeIngredients = listOf("Recipe ingredient 1", "Recipe ingredient 2"),
isRecipePublic = false,
areCommentsDisabled = true,
)
assertThat(request.toInput()).isEqualTo(expected)
assertThat(request.toDraft()).isEqualTo(expected)
}
}