Simplify network layer (#175)
* Use Ktor for network requests * Remove V0 version * Remove Retrofit dependency * Fix url * Update versions of dependencies * Revert kotlinx-datetime Due to https://github.com/Kotlin/kotlinx-datetime/issues/304 * Rename leftovers * Remove OkHttp * Remove unused manifest * Remove unused Hilt module * Fix building empty image URLs * Use OkHttp as engine for Ktor * Reduce visibility of internal classes * Fix first set up test * Store only auth token, not header * Remove UnitInfo/FoodInfo/VersionInfo/NewShoppingListItemInfo * Remove RecipeSummaryInfo and ShoppingListsInfo * Remove FullShoppingListInfo * Remove ParseRecipeURLInfo * Remove FullRecipeInfo * Sign out if access token does not work * Rename getVersionInfo method * Update version name
This commit is contained in:
@@ -9,7 +9,7 @@ interface AuthRepo : ShoppingListsAuthRepo {
|
||||
|
||||
suspend fun authenticate(email: String, password: String)
|
||||
|
||||
suspend fun getAuthHeader(): String?
|
||||
suspend fun getAuthToken(): String?
|
||||
|
||||
suspend fun logout()
|
||||
}
|
||||
@@ -4,9 +4,9 @@ import kotlinx.coroutines.flow.Flow
|
||||
|
||||
interface AuthStorage {
|
||||
|
||||
val authHeaderFlow: Flow<String?>
|
||||
val authTokenFlow: Flow<String?>
|
||||
|
||||
suspend fun setAuthHeader(authHeader: String?)
|
||||
suspend fun setAuthToken(authToken: String?)
|
||||
|
||||
suspend fun getAuthHeader(): String?
|
||||
suspend fun getAuthToken(): String?
|
||||
}
|
||||
@@ -1,32 +1,19 @@
|
||||
package gq.kirmanak.mealient.data.auth.impl
|
||||
|
||||
import gq.kirmanak.mealient.data.auth.AuthDataSource
|
||||
import gq.kirmanak.mealient.data.baseurl.ServerInfoRepo
|
||||
import gq.kirmanak.mealient.data.baseurl.ServerVersion
|
||||
import gq.kirmanak.mealient.datasource.v0.MealieDataSourceV0
|
||||
import gq.kirmanak.mealient.datasource.v0.models.CreateApiTokenRequestV0
|
||||
import gq.kirmanak.mealient.datasource.v1.MealieDataSourceV1
|
||||
import gq.kirmanak.mealient.datasource.v1.models.CreateApiTokenRequestV1
|
||||
import gq.kirmanak.mealient.datasource.MealieDataSource
|
||||
import gq.kirmanak.mealient.datasource.models.CreateApiTokenRequest
|
||||
import javax.inject.Inject
|
||||
|
||||
class AuthDataSourceImpl @Inject constructor(
|
||||
private val serverInfoRepo: ServerInfoRepo,
|
||||
private val v0Source: MealieDataSourceV0,
|
||||
private val v1Source: MealieDataSourceV1,
|
||||
private val dataSource: MealieDataSource,
|
||||
) : AuthDataSource {
|
||||
|
||||
private suspend fun getVersion(): ServerVersion = serverInfoRepo.getVersion()
|
||||
|
||||
override suspend fun authenticate(
|
||||
username: String,
|
||||
password: String,
|
||||
): String = when (getVersion()) {
|
||||
ServerVersion.V0 -> v0Source.authenticate(username, password)
|
||||
ServerVersion.V1 -> v1Source.authenticate(username, password)
|
||||
override suspend fun authenticate(username: String, password: String): String {
|
||||
return dataSource.authenticate(username, password)
|
||||
}
|
||||
|
||||
override suspend fun createApiToken(name: String): String = when (getVersion()) {
|
||||
ServerVersion.V0 -> v0Source.createApiToken(CreateApiTokenRequestV0(name)).token
|
||||
ServerVersion.V1 -> v1Source.createApiToken(CreateApiTokenRequestV1(name)).token
|
||||
override suspend fun createApiToken(name: String): String {
|
||||
return dataSource.createApiToken(CreateApiTokenRequest(name)).token
|
||||
}
|
||||
}
|
||||
@@ -4,6 +4,7 @@ import gq.kirmanak.mealient.data.auth.AuthDataSource
|
||||
import gq.kirmanak.mealient.data.auth.AuthRepo
|
||||
import gq.kirmanak.mealient.data.auth.AuthStorage
|
||||
import gq.kirmanak.mealient.datasource.AuthenticationProvider
|
||||
import gq.kirmanak.mealient.datasource.SignOutHandler
|
||||
import gq.kirmanak.mealient.logging.Logger
|
||||
import kotlinx.coroutines.flow.Flow
|
||||
import kotlinx.coroutines.flow.map
|
||||
@@ -13,28 +14,29 @@ class AuthRepoImpl @Inject constructor(
|
||||
private val authStorage: AuthStorage,
|
||||
private val authDataSource: AuthDataSource,
|
||||
private val logger: Logger,
|
||||
private val signOutHandler: SignOutHandler,
|
||||
) : AuthRepo, AuthenticationProvider {
|
||||
|
||||
override val isAuthorizedFlow: Flow<Boolean>
|
||||
get() = authStorage.authHeaderFlow.map { it != null }
|
||||
get() = authStorage.authTokenFlow.map { it != null }
|
||||
|
||||
override suspend fun authenticate(email: String, password: String) {
|
||||
logger.v { "authenticate() called with: email = $email, password = $password" }
|
||||
val token = authDataSource.authenticate(email, password)
|
||||
authStorage.setAuthHeader(AUTH_HEADER_FORMAT.format(token))
|
||||
authStorage.setAuthToken(token)
|
||||
val apiToken = authDataSource.createApiToken(API_TOKEN_NAME)
|
||||
authStorage.setAuthHeader(AUTH_HEADER_FORMAT.format(apiToken))
|
||||
authStorage.setAuthToken(apiToken)
|
||||
}
|
||||
|
||||
override suspend fun getAuthHeader(): String? = authStorage.getAuthHeader()
|
||||
override suspend fun getAuthToken(): String? = authStorage.getAuthToken()
|
||||
|
||||
override suspend fun logout() {
|
||||
logger.v { "logout() called" }
|
||||
authStorage.setAuthHeader(null)
|
||||
authStorage.setAuthToken(null)
|
||||
signOutHandler.signOut()
|
||||
}
|
||||
|
||||
companion object {
|
||||
private const val AUTH_HEADER_FORMAT = "Bearer %s"
|
||||
private const val API_TOKEN_NAME = "Mealient"
|
||||
}
|
||||
}
|
||||
@@ -22,15 +22,15 @@ class AuthStorageImpl @Inject constructor(
|
||||
private val logger: Logger,
|
||||
) : AuthStorage {
|
||||
|
||||
override val authHeaderFlow: Flow<String?>
|
||||
override val authTokenFlow: Flow<String?>
|
||||
get() = sharedPreferences
|
||||
.prefsChangeFlow(logger) { getString(AUTH_HEADER_KEY, null) }
|
||||
.prefsChangeFlow(logger) { getString(AUTH_TOKEN_KEY, null) }
|
||||
.distinctUntilChanged()
|
||||
private val singleThreadDispatcher = Executors.newSingleThreadExecutor().asCoroutineDispatcher()
|
||||
|
||||
override suspend fun setAuthHeader(authHeader: String?) = putString(AUTH_HEADER_KEY, authHeader)
|
||||
override suspend fun setAuthToken(authToken: String?) = putString(AUTH_TOKEN_KEY, authToken)
|
||||
|
||||
override suspend fun getAuthHeader(): String? = getString(AUTH_HEADER_KEY)
|
||||
override suspend fun getAuthToken(): String? = getString(AUTH_TOKEN_KEY)
|
||||
|
||||
private suspend fun putString(
|
||||
key: String,
|
||||
@@ -48,6 +48,6 @@ class AuthStorageImpl @Inject constructor(
|
||||
|
||||
companion object {
|
||||
@VisibleForTesting
|
||||
const val AUTH_HEADER_KEY = "authHeader"
|
||||
const val AUTH_TOKEN_KEY = "authToken"
|
||||
}
|
||||
}
|
||||
@@ -1,16 +1,10 @@
|
||||
package gq.kirmanak.mealient.data.baseurl
|
||||
|
||||
import kotlinx.coroutines.flow.Flow
|
||||
|
||||
interface ServerInfoRepo {
|
||||
|
||||
suspend fun getUrl(): String?
|
||||
|
||||
suspend fun getVersion(): ServerVersion
|
||||
|
||||
suspend fun tryBaseURL(baseURL: String): Result<Unit>
|
||||
|
||||
fun versionUpdates(): Flow<ServerVersion>
|
||||
|
||||
}
|
||||
|
||||
|
||||
@@ -1,11 +1,7 @@
|
||||
package gq.kirmanak.mealient.data.baseurl
|
||||
|
||||
import gq.kirmanak.mealient.datasource.ServerUrlProvider
|
||||
import gq.kirmanak.mealient.datasource.runCatchingExceptCancel
|
||||
import gq.kirmanak.mealient.logging.Logger
|
||||
import kotlinx.coroutines.flow.Flow
|
||||
import kotlinx.coroutines.flow.filterNotNull
|
||||
import kotlinx.coroutines.flow.map
|
||||
import javax.inject.Inject
|
||||
|
||||
class ServerInfoRepoImpl @Inject constructor(
|
||||
@@ -20,47 +16,18 @@ class ServerInfoRepoImpl @Inject constructor(
|
||||
return result
|
||||
}
|
||||
|
||||
override suspend fun getVersion(): ServerVersion {
|
||||
var version = serverInfoStorage.getServerVersion()
|
||||
val serverVersion = if (version == null) {
|
||||
logger.d { "getVersion: version is null, requesting" }
|
||||
version = versionDataSource.getVersionInfo().version
|
||||
val result = determineServerVersion(version)
|
||||
serverInfoStorage.storeServerVersion(version)
|
||||
result
|
||||
} else {
|
||||
determineServerVersion(version)
|
||||
}
|
||||
logger.v { "getVersion() returned: $serverVersion from $version" }
|
||||
return serverVersion
|
||||
}
|
||||
|
||||
private fun determineServerVersion(version: String): ServerVersion = when {
|
||||
version.startsWith("v0") -> ServerVersion.V0
|
||||
version.startsWith("v1") -> ServerVersion.V1
|
||||
else -> {
|
||||
logger.w { "Unknown server version: $version" }
|
||||
ServerVersion.V1
|
||||
}
|
||||
}
|
||||
|
||||
override suspend fun tryBaseURL(baseURL: String): Result<Unit> {
|
||||
val oldVersion = serverInfoStorage.getServerVersion()
|
||||
val oldBaseUrl = serverInfoStorage.getBaseURL()
|
||||
serverInfoStorage.storeBaseURL(baseURL)
|
||||
|
||||
return runCatchingExceptCancel {
|
||||
serverInfoStorage.storeBaseURL(baseURL)
|
||||
val version = versionDataSource.getVersionInfo().version
|
||||
serverInfoStorage.storeServerVersion(version)
|
||||
}.onFailure {
|
||||
serverInfoStorage.storeBaseURL(oldBaseUrl, oldVersion)
|
||||
try {
|
||||
versionDataSource.requestVersion()
|
||||
} catch (e: Throwable) {
|
||||
serverInfoStorage.storeBaseURL(oldBaseUrl)
|
||||
return Result.failure(e)
|
||||
}
|
||||
|
||||
return Result.success(Unit)
|
||||
}
|
||||
|
||||
override fun versionUpdates(): Flow<ServerVersion> {
|
||||
return serverInfoStorage
|
||||
.serverVersionUpdates()
|
||||
.filterNotNull()
|
||||
.map { determineServerVersion(it) }
|
||||
}
|
||||
}
|
||||
@@ -1,18 +1,9 @@
|
||||
package gq.kirmanak.mealient.data.baseurl
|
||||
|
||||
import kotlinx.coroutines.flow.Flow
|
||||
|
||||
interface ServerInfoStorage {
|
||||
|
||||
suspend fun getBaseURL(): String?
|
||||
|
||||
suspend fun storeBaseURL(baseURL: String)
|
||||
suspend fun storeBaseURL(baseURL: String?)
|
||||
|
||||
suspend fun storeBaseURL(baseURL: String?, version: String?)
|
||||
|
||||
suspend fun storeServerVersion(version: String)
|
||||
|
||||
suspend fun getServerVersion(): String?
|
||||
|
||||
fun serverVersionUpdates(): Flow<String?>
|
||||
}
|
||||
@@ -1,3 +0,0 @@
|
||||
package gq.kirmanak.mealient.data.baseurl
|
||||
|
||||
enum class ServerVersion { V0, V1 }
|
||||
@@ -1,8 +1,8 @@
|
||||
package gq.kirmanak.mealient.data.baseurl
|
||||
|
||||
import gq.kirmanak.mealient.datasource.models.VersionInfo
|
||||
import gq.kirmanak.mealient.datasource.models.VersionResponse
|
||||
|
||||
interface VersionDataSource {
|
||||
|
||||
suspend fun getVersionInfo(): VersionInfo
|
||||
suspend fun requestVersion(): VersionResponse
|
||||
}
|
||||
@@ -1,37 +1,14 @@
|
||||
package gq.kirmanak.mealient.data.baseurl
|
||||
|
||||
import gq.kirmanak.mealient.datasource.models.VersionInfo
|
||||
import gq.kirmanak.mealient.datasource.runCatchingExceptCancel
|
||||
import gq.kirmanak.mealient.datasource.v0.MealieDataSourceV0
|
||||
import gq.kirmanak.mealient.datasource.v1.MealieDataSourceV1
|
||||
import gq.kirmanak.mealient.model_mapper.ModelMapper
|
||||
import kotlinx.coroutines.async
|
||||
import kotlinx.coroutines.awaitAll
|
||||
import kotlinx.coroutines.coroutineScope
|
||||
import gq.kirmanak.mealient.datasource.MealieDataSource
|
||||
import gq.kirmanak.mealient.datasource.models.VersionResponse
|
||||
import javax.inject.Inject
|
||||
|
||||
class VersionDataSourceImpl @Inject constructor(
|
||||
private val v0Source: MealieDataSourceV0,
|
||||
private val v1Source: MealieDataSourceV1,
|
||||
private val modelMapper: ModelMapper,
|
||||
private val dataSource: MealieDataSource,
|
||||
) : VersionDataSource {
|
||||
|
||||
override suspend fun getVersionInfo(): VersionInfo {
|
||||
val responses = coroutineScope {
|
||||
val v0Deferred = async {
|
||||
runCatchingExceptCancel { modelMapper.toVersionInfo(v0Source.getVersionInfo()) }
|
||||
}
|
||||
val v1Deferred = async {
|
||||
runCatchingExceptCancel { modelMapper.toVersionInfo(v1Source.getVersionInfo()) }
|
||||
}
|
||||
listOf(v0Deferred, v1Deferred).awaitAll()
|
||||
}
|
||||
val firstSuccess = responses.firstNotNullOfOrNull { it.getOrNull() }
|
||||
if (firstSuccess == null) {
|
||||
throw responses.firstNotNullOf { it.exceptionOrNull() }
|
||||
} else {
|
||||
return firstSuccess
|
||||
}
|
||||
override suspend fun requestVersion(): VersionResponse {
|
||||
return dataSource.getVersionInfo()
|
||||
}
|
||||
|
||||
}
|
||||
@@ -3,7 +3,6 @@ package gq.kirmanak.mealient.data.baseurl.impl
|
||||
import androidx.datastore.preferences.core.Preferences
|
||||
import gq.kirmanak.mealient.data.baseurl.ServerInfoStorage
|
||||
import gq.kirmanak.mealient.data.storage.PreferencesStorage
|
||||
import kotlinx.coroutines.flow.Flow
|
||||
import javax.inject.Inject
|
||||
|
||||
class ServerInfoStorageImpl @Inject constructor(
|
||||
@@ -13,45 +12,16 @@ class ServerInfoStorageImpl @Inject constructor(
|
||||
private val baseUrlKey: Preferences.Key<String>
|
||||
get() = preferencesStorage.baseUrlKey
|
||||
|
||||
private val serverVersionKey: Preferences.Key<String>
|
||||
get() = preferencesStorage.serverVersionKey
|
||||
|
||||
override suspend fun getBaseURL(): String? = getValue(baseUrlKey)
|
||||
|
||||
override suspend fun storeBaseURL(baseURL: String) {
|
||||
preferencesStorage.storeValues(Pair(baseUrlKey, baseURL))
|
||||
preferencesStorage.removeValues(serverVersionKey)
|
||||
}
|
||||
|
||||
override suspend fun storeBaseURL(baseURL: String?, version: String?) {
|
||||
when {
|
||||
baseURL == null -> {
|
||||
preferencesStorage.removeValues(baseUrlKey, serverVersionKey)
|
||||
}
|
||||
|
||||
version != null -> {
|
||||
preferencesStorage.storeValues(
|
||||
Pair(baseUrlKey, baseURL), Pair(serverVersionKey, version)
|
||||
)
|
||||
}
|
||||
|
||||
else -> {
|
||||
preferencesStorage.removeValues(serverVersionKey)
|
||||
preferencesStorage.storeValues(Pair(baseUrlKey, baseURL))
|
||||
}
|
||||
override suspend fun storeBaseURL(baseURL: String?) {
|
||||
if (baseURL == null) {
|
||||
preferencesStorage.removeValues(baseUrlKey)
|
||||
} else {
|
||||
preferencesStorage.storeValues(Pair(baseUrlKey, baseURL))
|
||||
}
|
||||
}
|
||||
|
||||
override suspend fun getServerVersion(): String? = getValue(serverVersionKey)
|
||||
|
||||
override suspend fun storeServerVersion(version: String) {
|
||||
preferencesStorage.storeValues(Pair(serverVersionKey, version))
|
||||
}
|
||||
|
||||
override fun serverVersionUpdates(): Flow<String?> {
|
||||
return preferencesStorage.valueUpdates(serverVersionKey)
|
||||
}
|
||||
|
||||
private suspend fun <T> getValue(key: Preferences.Key<T>): T? = preferencesStorage.getValue(key)
|
||||
|
||||
}
|
||||
@@ -0,0 +1,32 @@
|
||||
package gq.kirmanak.mealient.data.migration
|
||||
|
||||
import android.content.SharedPreferences
|
||||
import androidx.core.content.edit
|
||||
import androidx.datastore.core.DataStore
|
||||
import androidx.datastore.preferences.core.Preferences
|
||||
import androidx.datastore.preferences.core.edit
|
||||
import androidx.datastore.preferences.core.stringPreferencesKey
|
||||
import gq.kirmanak.mealient.datastore.DataStoreModule
|
||||
import javax.inject.Inject
|
||||
import javax.inject.Named
|
||||
|
||||
class From30MigrationExecutor @Inject constructor(
|
||||
@Named(DataStoreModule.ENCRYPTED) private val sharedPreferences: SharedPreferences,
|
||||
private val dataStore: DataStore<Preferences>,
|
||||
) : MigrationExecutor {
|
||||
|
||||
override val migratingFrom: Int = 30
|
||||
|
||||
override suspend fun executeMigration() {
|
||||
dataStore.edit { prefs ->
|
||||
prefs -= stringPreferencesKey("serverVersion")
|
||||
}
|
||||
val authHeader = sharedPreferences.getString("authHeader", null)
|
||||
if (authHeader != null) {
|
||||
sharedPreferences.edit {
|
||||
val authToken = authHeader.removePrefix("Bearer ")
|
||||
putString("authToken", authToken)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,92 +1,58 @@
|
||||
package gq.kirmanak.mealient.data.network
|
||||
|
||||
import gq.kirmanak.mealient.data.add.AddRecipeDataSource
|
||||
import gq.kirmanak.mealient.data.baseurl.ServerInfoRepo
|
||||
import gq.kirmanak.mealient.data.baseurl.ServerVersion
|
||||
import gq.kirmanak.mealient.data.recipes.network.RecipeDataSource
|
||||
import gq.kirmanak.mealient.data.share.ParseRecipeDataSource
|
||||
import gq.kirmanak.mealient.datasource.MealieDataSource
|
||||
import gq.kirmanak.mealient.datasource.models.AddRecipeInfo
|
||||
import gq.kirmanak.mealient.datasource.models.FullRecipeInfo
|
||||
import gq.kirmanak.mealient.datasource.models.ParseRecipeURLInfo
|
||||
import gq.kirmanak.mealient.datasource.models.RecipeSummaryInfo
|
||||
import gq.kirmanak.mealient.datasource.v0.MealieDataSourceV0
|
||||
import gq.kirmanak.mealient.datasource.v1.MealieDataSourceV1
|
||||
import gq.kirmanak.mealient.datasource.models.GetRecipeResponse
|
||||
import gq.kirmanak.mealient.datasource.models.GetRecipeSummaryResponse
|
||||
import gq.kirmanak.mealient.datasource.models.ParseRecipeURLRequest
|
||||
import gq.kirmanak.mealient.model_mapper.ModelMapper
|
||||
import javax.inject.Inject
|
||||
|
||||
class MealieDataSourceWrapper @Inject constructor(
|
||||
private val serverInfoRepo: ServerInfoRepo,
|
||||
private val v0Source: MealieDataSourceV0,
|
||||
private val v1Source: MealieDataSourceV1,
|
||||
private val dataSource: MealieDataSource,
|
||||
private val modelMapper: ModelMapper,
|
||||
) : AddRecipeDataSource, RecipeDataSource, ParseRecipeDataSource {
|
||||
|
||||
private suspend fun getVersion(): ServerVersion = serverInfoRepo.getVersion()
|
||||
|
||||
override suspend fun addRecipe(recipe: AddRecipeInfo): String = when (getVersion()) {
|
||||
ServerVersion.V0 -> v0Source.addRecipe(modelMapper.toV0Request(recipe))
|
||||
ServerVersion.V1 -> {
|
||||
val slug = v1Source.createRecipe(modelMapper.toV1CreateRequest(recipe))
|
||||
v1Source.updateRecipe(slug, modelMapper.toV1UpdateRequest(recipe))
|
||||
slug
|
||||
}
|
||||
override suspend fun addRecipe(recipe: AddRecipeInfo): String {
|
||||
val slug = dataSource.createRecipe(modelMapper.toCreateRequest(recipe))
|
||||
dataSource.updateRecipe(slug, modelMapper.toUpdateRequest(recipe))
|
||||
return slug
|
||||
}
|
||||
|
||||
override suspend fun requestRecipes(
|
||||
start: Int,
|
||||
limit: Int,
|
||||
): List<RecipeSummaryInfo> = when (getVersion()) {
|
||||
ServerVersion.V0 -> {
|
||||
v0Source.requestRecipes(start, limit).map { modelMapper.toRecipeSummaryInfo(it) }
|
||||
}
|
||||
ServerVersion.V1 -> {
|
||||
// Imagine start is 30 and limit is 15. It means that we already have page 1 and 2, now we need page 3
|
||||
val page = start / limit + 1
|
||||
v1Source.requestRecipes(page, limit).map { modelMapper.toRecipeSummaryInfo(it) }
|
||||
): List<GetRecipeSummaryResponse> {
|
||||
// Imagine start is 30 and limit is 15. It means that we already have page 1 and 2, now we need page 3
|
||||
val page = start / limit + 1
|
||||
return dataSource.requestRecipes(page, limit)
|
||||
}
|
||||
|
||||
override suspend fun requestRecipe(slug: String): GetRecipeResponse {
|
||||
return dataSource.requestRecipeInfo(slug)
|
||||
}
|
||||
|
||||
override suspend fun parseRecipeFromURL(parseRecipeURLInfo: ParseRecipeURLRequest): String {
|
||||
return dataSource.parseRecipeFromURL(parseRecipeURLInfo)
|
||||
}
|
||||
|
||||
override suspend fun getFavoriteRecipes(): List<String> {
|
||||
return dataSource.requestUserInfo().favoriteRecipes
|
||||
}
|
||||
|
||||
override suspend fun updateIsRecipeFavorite(recipeSlug: String, isFavorite: Boolean) {
|
||||
val userId = dataSource.requestUserInfo().id
|
||||
if (isFavorite) {
|
||||
dataSource.addFavoriteRecipe(userId, recipeSlug)
|
||||
} else {
|
||||
dataSource.removeFavoriteRecipe(userId, recipeSlug)
|
||||
}
|
||||
}
|
||||
|
||||
override suspend fun requestRecipeInfo(slug: String): FullRecipeInfo = when (getVersion()) {
|
||||
ServerVersion.V0 -> modelMapper.toFullRecipeInfo(v0Source.requestRecipeInfo(slug))
|
||||
ServerVersion.V1 -> modelMapper.toFullRecipeInfo(v1Source.requestRecipeInfo(slug))
|
||||
}
|
||||
|
||||
override suspend fun parseRecipeFromURL(
|
||||
parseRecipeURLInfo: ParseRecipeURLInfo,
|
||||
): String = when (getVersion()) {
|
||||
ServerVersion.V0 -> v0Source.parseRecipeFromURL(modelMapper.toV0Request(parseRecipeURLInfo))
|
||||
ServerVersion.V1 -> v1Source.parseRecipeFromURL(modelMapper.toV1Request(parseRecipeURLInfo))
|
||||
}
|
||||
|
||||
override suspend fun getFavoriteRecipes(): List<String> = when (getVersion()) {
|
||||
ServerVersion.V0 -> v0Source.requestUserInfo().favoriteRecipes
|
||||
ServerVersion.V1 -> v1Source.requestUserInfo().favoriteRecipes
|
||||
}
|
||||
|
||||
override suspend fun updateIsRecipeFavorite(
|
||||
recipeSlug: String,
|
||||
isFavorite: Boolean
|
||||
) = when (getVersion()) {
|
||||
ServerVersion.V0 -> {
|
||||
val userId = v0Source.requestUserInfo().id
|
||||
if (isFavorite) {
|
||||
v0Source.addFavoriteRecipe(userId, recipeSlug)
|
||||
} else {
|
||||
v0Source.removeFavoriteRecipe(userId, recipeSlug)
|
||||
}
|
||||
}
|
||||
ServerVersion.V1 -> {
|
||||
val userId = v1Source.requestUserInfo().id
|
||||
if (isFavorite) {
|
||||
v1Source.addFavoriteRecipe(userId, recipeSlug)
|
||||
} else {
|
||||
v1Source.removeFavoriteRecipe(userId, recipeSlug)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
override suspend fun deleteRecipe(recipeSlug: String) = when (getVersion()) {
|
||||
ServerVersion.V0 -> v0Source.deleteRecipe(recipeSlug)
|
||||
ServerVersion.V1 -> v1Source.deleteRecipe(recipeSlug)
|
||||
override suspend fun deleteRecipe(recipeSlug: String) {
|
||||
dataSource.deleteRecipe(recipeSlug)
|
||||
}
|
||||
}
|
||||
@@ -1,8 +1,8 @@
|
||||
package gq.kirmanak.mealient.data.recipes.impl
|
||||
|
||||
import android.net.Uri
|
||||
import gq.kirmanak.mealient.data.baseurl.ServerInfoRepo
|
||||
import gq.kirmanak.mealient.logging.Logger
|
||||
import okhttp3.HttpUrl.Companion.toHttpUrlOrNull
|
||||
import javax.inject.Inject
|
||||
|
||||
class RecipeImageUrlProviderImpl @Inject constructor(
|
||||
@@ -15,9 +15,11 @@ class RecipeImageUrlProviderImpl @Inject constructor(
|
||||
slug?.takeUnless { it.isBlank() } ?: return null
|
||||
val imagePath = IMAGE_PATH_FORMAT.format(slug)
|
||||
val baseUrl = serverInfoRepo.getUrl()?.takeUnless { it.isEmpty() }
|
||||
val result = baseUrl?.toHttpUrlOrNull()
|
||||
?.newBuilder()
|
||||
?.addPathSegments(imagePath)
|
||||
val result = baseUrl
|
||||
?.takeUnless { it.isBlank() }
|
||||
?.let { Uri.parse(it) }
|
||||
?.buildUpon()
|
||||
?.path(imagePath)
|
||||
?.build()
|
||||
?.toString()
|
||||
logger.v { "getRecipeImageUrl() returned: $result" }
|
||||
|
||||
@@ -45,7 +45,7 @@ class RecipeRepoImpl @Inject constructor(
|
||||
override suspend fun refreshRecipeInfo(recipeSlug: String): Result<Unit> {
|
||||
logger.v { "refreshRecipeInfo() called with: recipeSlug = $recipeSlug" }
|
||||
return runCatchingExceptCancel {
|
||||
val info = dataSource.requestRecipeInfo(recipeSlug)
|
||||
val info = dataSource.requestRecipe(recipeSlug)
|
||||
val entity = modelMapper.toRecipeEntity(info)
|
||||
val ingredients = info.recipeIngredients.map {
|
||||
modelMapper.toRecipeIngredientEntity(it, entity.remoteId)
|
||||
|
||||
@@ -1,12 +1,12 @@
|
||||
package gq.kirmanak.mealient.data.recipes.network
|
||||
|
||||
import gq.kirmanak.mealient.datasource.models.FullRecipeInfo
|
||||
import gq.kirmanak.mealient.datasource.models.RecipeSummaryInfo
|
||||
import gq.kirmanak.mealient.datasource.models.GetRecipeResponse
|
||||
import gq.kirmanak.mealient.datasource.models.GetRecipeSummaryResponse
|
||||
|
||||
interface RecipeDataSource {
|
||||
suspend fun requestRecipes(start: Int, limit: Int): List<RecipeSummaryInfo>
|
||||
suspend fun requestRecipes(start: Int, limit: Int): List<GetRecipeSummaryResponse>
|
||||
|
||||
suspend fun requestRecipeInfo(slug: String): FullRecipeInfo
|
||||
suspend fun requestRecipe(slug: String): GetRecipeResponse
|
||||
|
||||
suspend fun getFavoriteRecipes(): List<String>
|
||||
|
||||
|
||||
@@ -1,8 +1,8 @@
|
||||
package gq.kirmanak.mealient.data.share
|
||||
|
||||
import gq.kirmanak.mealient.datasource.models.ParseRecipeURLInfo
|
||||
import gq.kirmanak.mealient.datasource.models.ParseRecipeURLRequest
|
||||
|
||||
interface ParseRecipeDataSource {
|
||||
|
||||
suspend fun parseRecipeFromURL(parseRecipeURLInfo: ParseRecipeURLInfo): String
|
||||
suspend fun parseRecipeFromURL(parseRecipeURLInfo: ParseRecipeURLRequest): String
|
||||
}
|
||||
@@ -1,7 +1,7 @@
|
||||
package gq.kirmanak.mealient.data.share
|
||||
|
||||
import androidx.core.util.PatternsCompat
|
||||
import gq.kirmanak.mealient.datasource.models.ParseRecipeURLInfo
|
||||
import gq.kirmanak.mealient.datasource.models.ParseRecipeURLRequest
|
||||
import gq.kirmanak.mealient.logging.Logger
|
||||
import javax.inject.Inject
|
||||
|
||||
@@ -15,7 +15,7 @@ class ShareRecipeRepoImpl @Inject constructor(
|
||||
val matcher = PatternsCompat.WEB_URL.matcher(url)
|
||||
require(matcher.find()) { "Can't find URL in the text" }
|
||||
val urlString = matcher.group()
|
||||
val request = ParseRecipeURLInfo(url = urlString, includeTags = true)
|
||||
val request = ParseRecipeURLRequest(url = urlString, includeTags = true)
|
||||
return parseRecipeDataSource.parseRecipeFromURL(request)
|
||||
}
|
||||
}
|
||||
@@ -7,8 +7,6 @@ interface PreferencesStorage {
|
||||
|
||||
val baseUrlKey: Preferences.Key<String>
|
||||
|
||||
val serverVersionKey: Preferences.Key<String>
|
||||
|
||||
val isDisclaimerAcceptedKey: Preferences.Key<Boolean>
|
||||
|
||||
val lastExecutedMigrationVersionKey: Preferences.Key<Int>
|
||||
|
||||
@@ -24,8 +24,6 @@ class PreferencesStorageImpl @Inject constructor(
|
||||
|
||||
override val baseUrlKey = stringPreferencesKey("baseUrl")
|
||||
|
||||
override val serverVersionKey = stringPreferencesKey("serverVersion")
|
||||
|
||||
override val isDisclaimerAcceptedKey = booleanPreferencesKey("isDisclaimedAccepted")
|
||||
|
||||
override val lastExecutedMigrationVersionKey: Preferences.Key<Int> =
|
||||
|
||||
@@ -6,6 +6,7 @@ import dagger.hilt.InstallIn
|
||||
import dagger.hilt.components.SingletonComponent
|
||||
import dagger.multibindings.IntoSet
|
||||
import gq.kirmanak.mealient.data.migration.From24AuthMigrationExecutor
|
||||
import gq.kirmanak.mealient.data.migration.From30MigrationExecutor
|
||||
import gq.kirmanak.mealient.data.migration.MigrationDetector
|
||||
import gq.kirmanak.mealient.data.migration.MigrationDetectorImpl
|
||||
import gq.kirmanak.mealient.data.migration.MigrationExecutor
|
||||
@@ -18,6 +19,10 @@ interface MigrationModule {
|
||||
@IntoSet
|
||||
fun bindFrom24AuthMigrationExecutor(from24AuthMigrationExecutor: From24AuthMigrationExecutor): MigrationExecutor
|
||||
|
||||
@Binds
|
||||
@IntoSet
|
||||
fun bindFrom30MigrationExecutor(impl: From30MigrationExecutor): MigrationExecutor
|
||||
|
||||
@Binds
|
||||
fun bindMigrationDetector(migrationDetectorImpl: MigrationDetectorImpl): MigrationDetector
|
||||
}
|
||||
@@ -109,7 +109,6 @@ class MainActivity : BaseActivity<MainActivityBinding>(
|
||||
when (itemId) {
|
||||
R.id.logout -> menuItem.isVisible = uiState.canShowLogout
|
||||
R.id.login -> menuItem.isVisible = uiState.canShowLogin
|
||||
R.id.shopping_lists -> menuItem.isVisible = uiState.v1MenuItemsVisible
|
||||
}
|
||||
menuItem.isChecked = itemId == checkedMenuItem
|
||||
}
|
||||
|
||||
@@ -8,7 +8,6 @@ import dagger.hilt.android.lifecycle.HiltViewModel
|
||||
import gq.kirmanak.mealient.R
|
||||
import gq.kirmanak.mealient.data.auth.AuthRepo
|
||||
import gq.kirmanak.mealient.data.baseurl.ServerInfoRepo
|
||||
import gq.kirmanak.mealient.data.baseurl.ServerVersion
|
||||
import gq.kirmanak.mealient.data.disclaimer.DisclaimerStorage
|
||||
import gq.kirmanak.mealient.data.recipes.RecipeRepo
|
||||
import gq.kirmanak.mealient.logging.Logger
|
||||
@@ -47,10 +46,6 @@ class MainActivityViewModel @Inject constructor(
|
||||
.onEach { isAuthorized -> updateUiState { it.copy(isAuthorized = isAuthorized) } }
|
||||
.launchIn(viewModelScope)
|
||||
|
||||
serverInfoRepo.versionUpdates()
|
||||
.onEach { version -> updateUiState { it.copy(v1MenuItemsVisible = version == ServerVersion.V1) } }
|
||||
.launchIn(viewModelScope)
|
||||
|
||||
viewModelScope.launch {
|
||||
_startDestination.value = when {
|
||||
!disclaimerStorage.isDisclaimerAccepted() -> {
|
||||
|
||||
@@ -14,7 +14,6 @@
|
||||
|
||||
<item
|
||||
android:id="@+id/shopping_lists"
|
||||
android:visible="false"
|
||||
android:checkable="true"
|
||||
android:icon="@drawable/ic_shopping_cart"
|
||||
android:title="@string/menu_navigation_drawer_shopping_lists" />
|
||||
|
||||
Reference in New Issue
Block a user