From 057651c60f3b3e44b7a20bd139c8fe6b9d55d667 Mon Sep 17 00:00:00 2001 From: Kirill Kamakin Date: Thu, 4 Aug 2022 21:19:15 +0200 Subject: [PATCH] Use intermediate representation for AddRecipe draft --- app/build.gradle.kts | 3 -- .../mealient/data/add/AddRecipeStorage.kt | 13 ------ .../data/add/impl/AddRecipeRepoImpl.kt | 32 ++++++++++++-- .../data/add/impl/AddRecipeStorageImpl.kt | 30 ------------- .../data/add/models/AddRecipeRequest.kt | 26 +++++------ .../data/auth/impl/AuthStorageImpl.kt | 2 +- .../kirmanak/mealient/di/AddRecipeModule.kt | 4 +- .../gq/kirmanak/mealient/di/AuthModule.kt | 21 --------- app/src/main/proto/AddRecipeInput.proto | 14 ------ .../data/add/models/AddRecipeRequestTest.kt | 39 +++++++++-------- .../mealient/datastore/DataStoreModule.kt | 23 ++++++++++ .../datastore/recipe/AddRecipeDraft.kt | 11 +++++ .../datastore/recipe/AddRecipeStorage.kt | 12 ++++++ .../datastore/recipe/AddRecipeStorageImpl.kt | 43 +++++++++++++++++++ 14 files changed, 154 insertions(+), 119 deletions(-) delete mode 100644 app/src/main/java/gq/kirmanak/mealient/data/add/AddRecipeStorage.kt delete mode 100644 app/src/main/java/gq/kirmanak/mealient/data/add/impl/AddRecipeStorageImpl.kt delete mode 100644 app/src/main/proto/AddRecipeInput.proto create mode 100644 datastore/src/main/kotlin/gq/kirmanak/mealient/datastore/recipe/AddRecipeDraft.kt create mode 100644 datastore/src/main/kotlin/gq/kirmanak/mealient/datastore/recipe/AddRecipeStorage.kt create mode 100644 datastore/src/main/kotlin/gq/kirmanak/mealient/datastore/recipe/AddRecipeStorageImpl.kt diff --git a/app/build.gradle.kts b/app/build.gradle.kts index c36411a..3c580bc 100644 --- a/app/build.gradle.kts +++ b/app/build.gradle.kts @@ -120,9 +120,6 @@ dependencies { implementation(libs.kirich1409.viewBinding) implementation(libs.androidx.datastore.preferences) - implementation(libs.androidx.datastore.datastore) - - implementation(libs.androidx.security.crypto) implementation(platform(libs.google.firebase.bom)) implementation(libs.google.firebase.analyticsKtx) diff --git a/app/src/main/java/gq/kirmanak/mealient/data/add/AddRecipeStorage.kt b/app/src/main/java/gq/kirmanak/mealient/data/add/AddRecipeStorage.kt deleted file mode 100644 index 283416a..0000000 --- a/app/src/main/java/gq/kirmanak/mealient/data/add/AddRecipeStorage.kt +++ /dev/null @@ -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 - - suspend fun save(addRecipeRequest: AddRecipeRequest) - - suspend fun clear() -} \ No newline at end of file diff --git a/app/src/main/java/gq/kirmanak/mealient/data/add/impl/AddRecipeRepoImpl.kt b/app/src/main/java/gq/kirmanak/mealient/data/add/impl/AddRecipeRepoImpl.kt index 372a8b6..d820b61 100644 --- a/app/src/main/java/gq/kirmanak/mealient/data/add/impl/AddRecipeRepoImpl.kt +++ b/app/src/main/java/gq/kirmanak/mealient/data/add/impl/AddRecipeRepoImpl.kt @@ -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 - 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() { diff --git a/app/src/main/java/gq/kirmanak/mealient/data/add/impl/AddRecipeStorageImpl.kt b/app/src/main/java/gq/kirmanak/mealient/data/add/impl/AddRecipeStorageImpl.kt deleted file mode 100644 index 800a887..0000000 --- a/app/src/main/java/gq/kirmanak/mealient/data/add/impl/AddRecipeStorageImpl.kt +++ /dev/null @@ -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, -) : AddRecipeStorage { - - override val updates: Flow - 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() } - } -} \ No newline at end of file diff --git a/app/src/main/java/gq/kirmanak/mealient/data/add/models/AddRecipeRequest.kt b/app/src/main/java/gq/kirmanak/mealient/data/add/models/AddRecipeRequest.kt index 4501015..08307af 100644 --- a/app/src/main/java/gq/kirmanak/mealient/data/add/models/AddRecipeRequest.kt +++ b/app/src/main/java/gq/kirmanak/mealient/data/add/models/AddRecipeRequest.kt @@ -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 = 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 diff --git a/app/src/main/java/gq/kirmanak/mealient/data/auth/impl/AuthStorageImpl.kt b/app/src/main/java/gq/kirmanak/mealient/data/auth/impl/AuthStorageImpl.kt index 6570e95..79c074f 100644 --- a/app/src/main/java/gq/kirmanak/mealient/data/auth/impl/AuthStorageImpl.kt +++ b/app/src/main/java/gq/kirmanak/mealient/data/auth/impl/AuthStorageImpl.kt @@ -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 diff --git a/app/src/main/java/gq/kirmanak/mealient/di/AddRecipeModule.kt b/app/src/main/java/gq/kirmanak/mealient/di/AddRecipeModule.kt index cfb7a66..5e9b25c 100644 --- a/app/src/main/java/gq/kirmanak/mealient/di/AddRecipeModule.kt +++ b/app/src/main/java/gq/kirmanak/mealient/di/AddRecipeModule.kt @@ -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 diff --git a/app/src/main/java/gq/kirmanak/mealient/di/AuthModule.kt b/app/src/main/java/gq/kirmanak/mealient/di/AuthModule.kt index cb87c5f..a6c144c 100644 --- a/app/src/main/java/gq/kirmanak/mealient/di/AuthModule.kt +++ b/app/src/main/java/gq/kirmanak/mealient/di/AuthModule.kt @@ -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 diff --git a/app/src/main/proto/AddRecipeInput.proto b/app/src/main/proto/AddRecipeInput.proto deleted file mode 100644 index 13df823..0000000 --- a/app/src/main/proto/AddRecipeInput.proto +++ /dev/null @@ -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; -} \ No newline at end of file diff --git a/app/src/test/java/gq/kirmanak/mealient/data/add/models/AddRecipeRequestTest.kt b/app/src/test/java/gq/kirmanak/mealient/data/add/models/AddRecipeRequestTest.kt index fa31ac6..78ab001 100644 --- a/app/src/test/java/gq/kirmanak/mealient/data/add/models/AddRecipeRequestTest.kt +++ b/app/src/test/java/gq/kirmanak/mealient/data/add/models/AddRecipeRequestTest.kt @@ -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) } } \ No newline at end of file diff --git a/datastore/src/main/kotlin/gq/kirmanak/mealient/datastore/DataStoreModule.kt b/datastore/src/main/kotlin/gq/kirmanak/mealient/datastore/DataStoreModule.kt index 3555021..2d0ba3c 100644 --- a/datastore/src/main/kotlin/gq/kirmanak/mealient/datastore/DataStoreModule.kt +++ b/datastore/src/main/kotlin/gq/kirmanak/mealient/datastore/DataStoreModule.kt @@ -1,9 +1,12 @@ package gq.kirmanak.mealient.datastore import android.content.Context +import android.content.SharedPreferences import androidx.datastore.core.DataStore import androidx.datastore.core.DataStoreFactory import androidx.datastore.dataStoreFile +import androidx.security.crypto.EncryptedSharedPreferences +import androidx.security.crypto.MasterKeys import dagger.Module import dagger.Provides import dagger.hilt.InstallIn @@ -11,6 +14,7 @@ import dagger.hilt.android.qualifiers.ApplicationContext import dagger.hilt.components.SingletonComponent import gq.kirmanak.mealient.datastore.recipe.AddRecipeInput import gq.kirmanak.mealient.datastore.recipe.AddRecipeInputSerializer +import javax.inject.Named import javax.inject.Singleton @Module @@ -18,6 +22,8 @@ import javax.inject.Singleton interface DataStoreModule { companion object { + const val ENCRYPTED = "encrypted" + @Provides @Singleton fun provideAddRecipeInputStore( @@ -25,5 +31,22 @@ interface DataStoreModule { ): DataStore = DataStoreFactory.create(AddRecipeInputSerializer) { context.dataStoreFile("add_recipe_input") } + + @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 + ) + } } } \ No newline at end of file diff --git a/datastore/src/main/kotlin/gq/kirmanak/mealient/datastore/recipe/AddRecipeDraft.kt b/datastore/src/main/kotlin/gq/kirmanak/mealient/datastore/recipe/AddRecipeDraft.kt new file mode 100644 index 0000000..57a6063 --- /dev/null +++ b/datastore/src/main/kotlin/gq/kirmanak/mealient/datastore/recipe/AddRecipeDraft.kt @@ -0,0 +1,11 @@ +package gq.kirmanak.mealient.datastore.recipe + +data class AddRecipeDraft( + val recipeName: String, + val recipeDescription: String, + val recipeYield: String, + val recipeInstructions: List, + val recipeIngredients: List, + val isRecipePublic: Boolean, + val areCommentsDisabled: Boolean, +) diff --git a/datastore/src/main/kotlin/gq/kirmanak/mealient/datastore/recipe/AddRecipeStorage.kt b/datastore/src/main/kotlin/gq/kirmanak/mealient/datastore/recipe/AddRecipeStorage.kt new file mode 100644 index 0000000..ddd446c --- /dev/null +++ b/datastore/src/main/kotlin/gq/kirmanak/mealient/datastore/recipe/AddRecipeStorage.kt @@ -0,0 +1,12 @@ +package gq.kirmanak.mealient.datastore.recipe + +import kotlinx.coroutines.flow.Flow + +interface AddRecipeStorage { + + val updates: Flow + + suspend fun save(addRecipeRequest: AddRecipeDraft) + + suspend fun clear() +} \ No newline at end of file diff --git a/datastore/src/main/kotlin/gq/kirmanak/mealient/datastore/recipe/AddRecipeStorageImpl.kt b/datastore/src/main/kotlin/gq/kirmanak/mealient/datastore/recipe/AddRecipeStorageImpl.kt new file mode 100644 index 0000000..c2539ef --- /dev/null +++ b/datastore/src/main/kotlin/gq/kirmanak/mealient/datastore/recipe/AddRecipeStorageImpl.kt @@ -0,0 +1,43 @@ +package gq.kirmanak.mealient.datastore.recipe + +import androidx.datastore.core.DataStore +import kotlinx.coroutines.flow.Flow +import kotlinx.coroutines.flow.map +import javax.inject.Inject +import javax.inject.Singleton + +@Singleton +class AddRecipeStorageImpl @Inject constructor( + private val dataStore: DataStore, +) : AddRecipeStorage { + + override val updates: Flow + get() = dataStore.data.map { + AddRecipeDraft( + recipeName = it.recipeName, + recipeDescription = it.recipeDescription, + recipeYield = it.recipeYield, + recipeInstructions = it.recipeInstructionsList, + recipeIngredients = it.recipeIngredientsList, + isRecipePublic = it.isRecipePublic, + areCommentsDisabled = it.areCommentsDisabled, + ) + } + + override suspend fun save(addRecipeRequest: AddRecipeDraft) { + val input = AddRecipeInput.newBuilder() + .setRecipeName(addRecipeRequest.recipeName) + .setRecipeDescription(addRecipeRequest.recipeDescription) + .setRecipeYield(addRecipeRequest.recipeYield) + .setIsRecipePublic(addRecipeRequest.isRecipePublic) + .setAreCommentsDisabled(addRecipeRequest.areCommentsDisabled) + .addAllRecipeIngredients(addRecipeRequest.recipeIngredients) + .addAllRecipeInstructions(addRecipeRequest.recipeInstructions) + .build() + dataStore.updateData { input } + } + + override suspend fun clear() { + dataStore.updateData { AddRecipeInput.getDefaultInstance() } + } +} \ No newline at end of file