Create network module
This commit is contained in:
1
datasource/.gitignore
vendored
Normal file
1
datasource/.gitignore
vendored
Normal file
@@ -0,0 +1 @@
|
||||
/build
|
||||
45
datasource/build.gradle.kts
Normal file
45
datasource/build.gradle.kts
Normal file
@@ -0,0 +1,45 @@
|
||||
plugins {
|
||||
id("gq.kirmanak.mealient.library")
|
||||
id("kotlin-kapt")
|
||||
id("dagger.hilt.android.plugin")
|
||||
id("org.jetbrains.kotlin.plugin.serialization")
|
||||
}
|
||||
|
||||
android {
|
||||
defaultConfig {
|
||||
buildConfigField("Boolean", "LOG_NETWORK", "false")
|
||||
}
|
||||
namespace = "gq.kirmanak.mealient.datasource"
|
||||
}
|
||||
|
||||
dependencies {
|
||||
implementation(project(":logging"))
|
||||
|
||||
implementation(libs.google.dagger.hiltAndroid)
|
||||
kapt(libs.google.dagger.hiltCompiler)
|
||||
kaptTest(libs.google.dagger.hiltAndroidCompiler)
|
||||
testImplementation(libs.google.dagger.hiltAndroidTesting)
|
||||
|
||||
implementation(libs.jetbrains.kotlinx.datetime)
|
||||
|
||||
implementation(libs.jetbrains.kotlinx.serialization)
|
||||
|
||||
implementation(libs.squareup.retrofit)
|
||||
|
||||
implementation(libs.jakewharton.retrofitSerialization)
|
||||
|
||||
implementation(platform(libs.okhttp3.bom))
|
||||
implementation(libs.okhttp3.okhttp)
|
||||
debugImplementation(libs.okhttp3.loggingInterceptor)
|
||||
|
||||
implementation(libs.jetbrains.kotlinx.coroutinesAndroid)
|
||||
testImplementation(libs.jetbrains.kotlinx.coroutinesTest)
|
||||
|
||||
testImplementation(libs.androidx.test.junit)
|
||||
|
||||
testImplementation(libs.google.truth)
|
||||
|
||||
testImplementation(libs.io.mockk)
|
||||
|
||||
debugImplementation(libs.chuckerteam.chucker)
|
||||
}
|
||||
@@ -0,0 +1,48 @@
|
||||
package gq.kirmanak.mealient
|
||||
|
||||
import android.content.Context
|
||||
import com.chuckerteam.chucker.api.ChuckerCollector
|
||||
import com.chuckerteam.chucker.api.ChuckerInterceptor
|
||||
import com.chuckerteam.chucker.api.RetentionManager
|
||||
import dagger.Module
|
||||
import dagger.Provides
|
||||
import dagger.hilt.InstallIn
|
||||
import dagger.hilt.android.qualifiers.ApplicationContext
|
||||
import dagger.hilt.components.SingletonComponent
|
||||
import dagger.multibindings.IntoSet
|
||||
import gq.kirmanak.mealient.datasource.BuildConfig
|
||||
import gq.kirmanak.mealient.logging.Logger
|
||||
import okhttp3.Interceptor
|
||||
import okhttp3.logging.HttpLoggingInterceptor
|
||||
import javax.inject.Singleton
|
||||
|
||||
@Module
|
||||
@InstallIn(SingletonComponent::class)
|
||||
object DebugModule {
|
||||
@Provides
|
||||
@Singleton
|
||||
@IntoSet
|
||||
fun provideLoggingInterceptor(logger: Logger): Interceptor {
|
||||
val interceptor = HttpLoggingInterceptor { message -> logger.v(tag = "OkHttp") { message } }
|
||||
interceptor.level = when {
|
||||
BuildConfig.LOG_NETWORK -> HttpLoggingInterceptor.Level.BODY
|
||||
else -> HttpLoggingInterceptor.Level.BASIC
|
||||
}
|
||||
return interceptor
|
||||
}
|
||||
|
||||
@Provides
|
||||
@Singleton
|
||||
@IntoSet
|
||||
fun provideChuckerInterceptor(@ApplicationContext context: Context): Interceptor {
|
||||
val collector = ChuckerCollector(
|
||||
context = context,
|
||||
showNotification = true,
|
||||
retentionPeriod = RetentionManager.Period.ONE_HOUR,
|
||||
)
|
||||
return ChuckerInterceptor.Builder(context)
|
||||
.collector(collector)
|
||||
.alwaysReadResponseBody(true)
|
||||
.build()
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,8 @@
|
||||
package gq.kirmanak.mealient.datasource
|
||||
|
||||
import okhttp3.Cache
|
||||
|
||||
interface CacheBuilder {
|
||||
|
||||
fun buildCache(): Cache
|
||||
}
|
||||
@@ -0,0 +1,41 @@
|
||||
package gq.kirmanak.mealient.datasource
|
||||
|
||||
import android.content.Context
|
||||
import android.os.StatFs
|
||||
import dagger.hilt.android.qualifiers.ApplicationContext
|
||||
import gq.kirmanak.mealient.logging.Logger
|
||||
import okhttp3.Cache
|
||||
import java.io.File
|
||||
import javax.inject.Inject
|
||||
import javax.inject.Singleton
|
||||
|
||||
@Singleton
|
||||
class CacheBuilderImpl @Inject constructor(
|
||||
@ApplicationContext private val context: Context,
|
||||
private val logger: Logger,
|
||||
) : CacheBuilder {
|
||||
|
||||
override fun buildCache(): Cache {
|
||||
val dir = findCacheDir()
|
||||
return Cache(dir, calculateDiskCacheSize(dir))
|
||||
}
|
||||
|
||||
private fun findCacheDir(): File = File(context.cacheDir, "okhttp")
|
||||
|
||||
private fun calculateDiskCacheSize(dir: File): Long = dir.runCatching {
|
||||
StatFs(absolutePath).let {
|
||||
it.blockCountLong * it.blockSizeLong * AVAILABLE_SPACE_PERCENT / 100
|
||||
}
|
||||
}
|
||||
.onFailure { logger.e(it) { "Can't get available space" } }
|
||||
.getOrNull()
|
||||
?.coerceAtLeast(MIN_OKHTTP_CACHE_SIZE)
|
||||
?.coerceAtMost(MAX_OKHTTP_CACHE_SIZE)
|
||||
?: MIN_OKHTTP_CACHE_SIZE
|
||||
|
||||
companion object {
|
||||
private const val MIN_OKHTTP_CACHE_SIZE = 5 * 1024 * 1024L // 5MB
|
||||
private const val MAX_OKHTTP_CACHE_SIZE = 50 * 1024 * 1024L // 50MB
|
||||
private const val AVAILABLE_SPACE_PERCENT = 2
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,68 @@
|
||||
package gq.kirmanak.mealient.datasource
|
||||
|
||||
import com.jakewharton.retrofit2.converter.kotlinx.serialization.asConverterFactory
|
||||
import dagger.Binds
|
||||
import dagger.Module
|
||||
import dagger.Provides
|
||||
import dagger.hilt.InstallIn
|
||||
import dagger.hilt.components.SingletonComponent
|
||||
import kotlinx.serialization.ExperimentalSerializationApi
|
||||
import kotlinx.serialization.json.Json
|
||||
import okhttp3.MediaType.Companion.toMediaType
|
||||
import okhttp3.OkHttpClient
|
||||
import retrofit2.Converter
|
||||
import retrofit2.Retrofit
|
||||
import retrofit2.create
|
||||
import javax.inject.Singleton
|
||||
|
||||
@Module
|
||||
@InstallIn(SingletonComponent::class)
|
||||
interface DataSourceModule {
|
||||
|
||||
companion object {
|
||||
|
||||
const val AUTHORIZATION_HEADER_NAME = "Authorization"
|
||||
|
||||
@Provides
|
||||
@Singleton
|
||||
fun provideJson(): Json = Json {
|
||||
coerceInputValues = true
|
||||
ignoreUnknownKeys = true
|
||||
encodeDefaults = true
|
||||
}
|
||||
|
||||
@OptIn(ExperimentalSerializationApi::class)
|
||||
@Provides
|
||||
@Singleton
|
||||
fun provideConverterFactory(json: Json): Converter.Factory =
|
||||
json.asConverterFactory("application/json".toMediaType())
|
||||
|
||||
@Provides
|
||||
@Singleton
|
||||
fun provideOkHttp(okHttpBuilder: OkHttpBuilder): OkHttpClient =
|
||||
okHttpBuilder.buildOkHttp()
|
||||
|
||||
@Provides
|
||||
@Singleton
|
||||
fun provideRetrofit(retrofitBuilder: RetrofitBuilder): Retrofit =
|
||||
retrofitBuilder.buildRetrofit("https://beta.mealie.io/")
|
||||
|
||||
@Provides
|
||||
@Singleton
|
||||
fun provideMealieService(retrofit: Retrofit): MealieService =
|
||||
retrofit.create()
|
||||
|
||||
}
|
||||
|
||||
@Binds
|
||||
@Singleton
|
||||
fun bindCacheBuilder(cacheBuilderImpl: CacheBuilderImpl): CacheBuilder
|
||||
|
||||
@Binds
|
||||
@Singleton
|
||||
fun bindOkHttpBuilder(okHttpBuilderImpl: OkHttpBuilderImpl): OkHttpBuilder
|
||||
|
||||
@Binds
|
||||
@Singleton
|
||||
fun bindMealieDataSource(mealientDataSourceImpl: MealieDataSourceImpl): MealieDataSource
|
||||
}
|
||||
@@ -0,0 +1,41 @@
|
||||
package gq.kirmanak.mealient.datasource
|
||||
|
||||
import gq.kirmanak.mealient.datasource.models.AddRecipeRequest
|
||||
import gq.kirmanak.mealient.datasource.models.GetRecipeResponse
|
||||
import gq.kirmanak.mealient.datasource.models.GetRecipeSummaryResponse
|
||||
import gq.kirmanak.mealient.datasource.models.VersionResponse
|
||||
|
||||
interface MealieDataSource {
|
||||
|
||||
suspend fun addRecipe(
|
||||
baseUrl: String,
|
||||
token: String?,
|
||||
recipe: AddRecipeRequest,
|
||||
): String
|
||||
|
||||
/**
|
||||
* Tries to acquire authentication token using the provided credentials
|
||||
*/
|
||||
suspend fun authenticate(
|
||||
baseUrl: String,
|
||||
username: String,
|
||||
password: String,
|
||||
): String
|
||||
|
||||
suspend fun getVersionInfo(
|
||||
baseUrl: String,
|
||||
): VersionResponse
|
||||
|
||||
suspend fun requestRecipes(
|
||||
baseUrl: String,
|
||||
token: String?,
|
||||
start: Int = 0,
|
||||
limit: Int = 9999,
|
||||
): List<GetRecipeSummaryResponse>
|
||||
|
||||
suspend fun requestRecipeInfo(
|
||||
baseUrl: String,
|
||||
token: String?,
|
||||
slug: String,
|
||||
): GetRecipeResponse
|
||||
}
|
||||
@@ -0,0 +1,106 @@
|
||||
package gq.kirmanak.mealient.datasource
|
||||
|
||||
import gq.kirmanak.mealient.datasource.models.*
|
||||
import gq.kirmanak.mealient.logging.Logger
|
||||
import kotlinx.serialization.ExperimentalSerializationApi
|
||||
import kotlinx.serialization.SerializationException
|
||||
import kotlinx.serialization.json.Json
|
||||
import kotlinx.serialization.json.decodeFromStream
|
||||
import retrofit2.HttpException
|
||||
import retrofit2.Response
|
||||
import javax.inject.Inject
|
||||
import javax.inject.Singleton
|
||||
|
||||
@Singleton
|
||||
class MealieDataSourceImpl @Inject constructor(
|
||||
private val logger: Logger,
|
||||
private val mealieService: MealieService,
|
||||
private val json: Json,
|
||||
) : MealieDataSource {
|
||||
|
||||
override suspend fun addRecipe(
|
||||
baseUrl: String, token: String?, recipe: AddRecipeRequest
|
||||
): String {
|
||||
logger.v { "addRecipe() called with: baseUrl = $baseUrl, token = $token, recipe = $recipe" }
|
||||
return mealieService.runCatching { addRecipe("$baseUrl/api/recipes/create", token, recipe) }
|
||||
.onFailure { logger.e(it) { "addRecipe() request failed with: baseUrl = $baseUrl, token = $token, recipe = $recipe" } }
|
||||
.onSuccess { logger.d { "addRecipe() request succeeded with: baseUrl = $baseUrl, token = $token, recipe = $recipe" } }
|
||||
.getOrThrowUnauthorized()
|
||||
}
|
||||
|
||||
override suspend fun authenticate(
|
||||
baseUrl: String, username: String, password: String
|
||||
): String {
|
||||
logger.v { "authenticate() called with: baseUrl = $baseUrl, username = $username, password = $password" }
|
||||
return mealieService.runCatching { getToken("$baseUrl/api/auth/token", username, password) }
|
||||
.onFailure { logger.e(it) { "authenticate() request failed with: baseUrl = $baseUrl, username = $username, password = $password" } }
|
||||
.onSuccess { logger.d { "authenticate() request succeeded with: baseUrl = $baseUrl, username = $username, password = $password" } }
|
||||
.mapCatching { parseToken(it) }
|
||||
.getOrThrowUnauthorized()
|
||||
}
|
||||
|
||||
override suspend fun getVersionInfo(baseUrl: String): VersionResponse {
|
||||
logger.v { "getVersionInfo() called with: baseUrl = $baseUrl" }
|
||||
return mealieService.runCatching { getVersion("$baseUrl/api/debug/version") }
|
||||
.onFailure { logger.e(it) { "getVersionInfo() request failed with: baseUrl = $baseUrl" } }
|
||||
.onSuccess { logger.d { "getVersionInfo() request succeeded with: baseUrl = $baseUrl" } }
|
||||
.getOrThrowUnauthorized()
|
||||
}
|
||||
|
||||
override suspend fun requestRecipes(
|
||||
baseUrl: String, token: String?, start: Int, limit: Int
|
||||
): List<GetRecipeSummaryResponse> {
|
||||
logger.v { "requestRecipes() called with: baseUrl = $baseUrl, token = $token, start = $start, limit = $limit" }
|
||||
return mealieService.runCatching {
|
||||
getRecipeSummary("$baseUrl/api/recipes/summary", token, start, limit)
|
||||
}
|
||||
.onFailure { logger.e(it) { "requestRecipes() request failed with: baseUrl = $baseUrl, token = $token, start = $start, limit = $limit" } }
|
||||
.onSuccess { logger.d { "requestRecipes() request succeeded with: baseUrl = $baseUrl, token = $token, start = $start, limit = $limit" } }
|
||||
.getOrThrowUnauthorized()
|
||||
}
|
||||
|
||||
override suspend fun requestRecipeInfo(
|
||||
baseUrl: String, token: String?, slug: String
|
||||
): GetRecipeResponse {
|
||||
logger.v { "requestRecipeInfo() called with: baseUrl = $baseUrl, token = $token, slug = $slug" }
|
||||
return mealieService.runCatching { getRecipe("$baseUrl/api/recipes/$slug", token) }
|
||||
.onFailure { logger.e(it) { "requestRecipeInfo() request failed with: baseUrl = $baseUrl, token = $token, slug = $slug" } }
|
||||
.onSuccess { logger.d { "requestRecipeInfo() request succeeded with: baseUrl = $baseUrl, token = $token, slug = $slug" } }
|
||||
.getOrThrowUnauthorized()
|
||||
}
|
||||
|
||||
private fun parseToken(
|
||||
response: Response<GetTokenResponse>
|
||||
): String = if (response.isSuccessful) {
|
||||
response.body()?.accessToken
|
||||
?: throw NetworkError.NotMealie(NullPointerException("Body is null"))
|
||||
} else {
|
||||
val cause = HttpException(response)
|
||||
val errorDetail = json.runCatching<Json, ErrorDetail> { decodeErrorBody(response) }
|
||||
.onFailure { logger.e(it) { "Can't decode error body" } }
|
||||
.getOrNull()
|
||||
throw when (errorDetail?.detail) {
|
||||
"Unauthorized" -> NetworkError.Unauthorized(cause)
|
||||
else -> NetworkError.NotMealie(cause)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private fun <T> Result<T>.getOrThrowUnauthorized(): T = getOrElse {
|
||||
throw if (it is HttpException && it.code() in listOf(401, 403)) {
|
||||
NetworkError.Unauthorized(it)
|
||||
} else {
|
||||
it
|
||||
}
|
||||
}
|
||||
|
||||
@OptIn(ExperimentalSerializationApi::class)
|
||||
private inline fun <T, reified R> Json.decodeErrorBody(response: Response<T>): R {
|
||||
val responseBody = checkNotNull(response.errorBody()) { "Can't decode absent error body" }
|
||||
return decodeFromStream(responseBody.byteStream())
|
||||
}
|
||||
|
||||
fun Throwable.mapToNetworkError(): NetworkError = when (this) {
|
||||
is HttpException, is SerializationException -> NetworkError.NotMealie(this)
|
||||
else -> NetworkError.NoServerConnection(this)
|
||||
}
|
||||
@@ -0,0 +1,43 @@
|
||||
package gq.kirmanak.mealient.datasource
|
||||
|
||||
import gq.kirmanak.mealient.datasource.DataSourceModule.Companion.AUTHORIZATION_HEADER_NAME
|
||||
import gq.kirmanak.mealient.datasource.models.*
|
||||
import retrofit2.Response
|
||||
import retrofit2.http.*
|
||||
|
||||
interface MealieService {
|
||||
|
||||
@FormUrlEncoded
|
||||
@POST
|
||||
suspend fun getToken(
|
||||
@Url url: String,
|
||||
@Field("username") username: String,
|
||||
@Field("password") password: String,
|
||||
): Response<GetTokenResponse>
|
||||
|
||||
@POST
|
||||
suspend fun addRecipe(
|
||||
@Url url: String,
|
||||
@Header(AUTHORIZATION_HEADER_NAME) token: String?,
|
||||
@Body addRecipeRequest: AddRecipeRequest,
|
||||
): String
|
||||
|
||||
@GET
|
||||
suspend fun getVersion(
|
||||
@Url url: String,
|
||||
): VersionResponse
|
||||
|
||||
@GET
|
||||
suspend fun getRecipeSummary(
|
||||
@Url url: String,
|
||||
@Header(AUTHORIZATION_HEADER_NAME) token: String?,
|
||||
@Query("start") start: Int,
|
||||
@Query("limit") limit: Int,
|
||||
): List<GetRecipeSummaryResponse>
|
||||
|
||||
@GET
|
||||
suspend fun getRecipe(
|
||||
@Url url: String,
|
||||
@Header(AUTHORIZATION_HEADER_NAME) token: String?,
|
||||
): GetRecipeResponse
|
||||
}
|
||||
@@ -0,0 +1,8 @@
|
||||
package gq.kirmanak.mealient.datasource
|
||||
|
||||
import okhttp3.OkHttpClient
|
||||
|
||||
interface OkHttpBuilder {
|
||||
|
||||
fun buildOkHttp(): OkHttpClient
|
||||
}
|
||||
@@ -0,0 +1,19 @@
|
||||
package gq.kirmanak.mealient.datasource
|
||||
|
||||
import okhttp3.Interceptor
|
||||
import okhttp3.OkHttpClient
|
||||
import javax.inject.Inject
|
||||
import javax.inject.Singleton
|
||||
|
||||
@Singleton
|
||||
class OkHttpBuilderImpl @Inject constructor(
|
||||
private val cacheBuilder: CacheBuilder,
|
||||
// Use @JvmSuppressWildcards because otherwise dagger can't inject it (https://stackoverflow.com/a/43149382)
|
||||
private val interceptors: Set<@JvmSuppressWildcards Interceptor>,
|
||||
) : OkHttpBuilder {
|
||||
|
||||
override fun buildOkHttp(): OkHttpClient = OkHttpClient.Builder()
|
||||
.apply { interceptors.forEach(::addNetworkInterceptor) }
|
||||
.cache(cacheBuilder.buildCache())
|
||||
.build()
|
||||
}
|
||||
@@ -0,0 +1,25 @@
|
||||
package gq.kirmanak.mealient.datasource
|
||||
|
||||
import gq.kirmanak.mealient.logging.Logger
|
||||
import okhttp3.OkHttpClient
|
||||
import retrofit2.Converter.Factory
|
||||
import retrofit2.Retrofit
|
||||
import javax.inject.Inject
|
||||
import javax.inject.Singleton
|
||||
|
||||
@Singleton
|
||||
class RetrofitBuilder @Inject constructor(
|
||||
private val okHttpClient: OkHttpClient,
|
||||
private val converterFactory: Factory,
|
||||
private val logger: Logger,
|
||||
) {
|
||||
|
||||
fun buildRetrofit(baseUrl: String): Retrofit {
|
||||
logger.v { "buildRetrofit() called with: baseUrl = $baseUrl" }
|
||||
return Retrofit.Builder()
|
||||
.baseUrl(baseUrl)
|
||||
.client(okHttpClient)
|
||||
.addConverterFactory(converterFactory)
|
||||
.build()
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,54 @@
|
||||
package gq.kirmanak.mealient.datasource.models
|
||||
|
||||
import kotlinx.serialization.SerialName
|
||||
import kotlinx.serialization.Serializable
|
||||
|
||||
@Serializable
|
||||
data class AddRecipeRequest(
|
||||
@SerialName("name") val name: String = "",
|
||||
@SerialName("description") val description: String = "",
|
||||
@SerialName("image") val image: String = "",
|
||||
@SerialName("recipeYield") val recipeYield: String = "",
|
||||
@SerialName("recipeIngredient") val recipeIngredient: List<AddRecipeIngredient> = emptyList(),
|
||||
@SerialName("recipeInstructions") val recipeInstructions: List<AddRecipeInstruction> = emptyList(),
|
||||
@SerialName("slug") val slug: String = "",
|
||||
@SerialName("filePath") val filePath: String = "",
|
||||
@SerialName("tags") val tags: List<String> = emptyList(),
|
||||
@SerialName("categories") val categories: List<String> = emptyList(),
|
||||
@SerialName("notes") val notes: List<AddRecipeNote> = emptyList(),
|
||||
@SerialName("extras") val extras: Map<String, String> = emptyMap(),
|
||||
@SerialName("assets") val assets: List<String> = emptyList(),
|
||||
@SerialName("settings") val settings: AddRecipeSettings = AddRecipeSettings(),
|
||||
)
|
||||
|
||||
@Serializable
|
||||
data class AddRecipeSettings(
|
||||
@SerialName("disableAmount") val disableAmount: Boolean = true,
|
||||
@SerialName("disableComments") val disableComments: Boolean = false,
|
||||
@SerialName("landscapeView") val landscapeView: Boolean = true,
|
||||
@SerialName("public") val public: Boolean = true,
|
||||
@SerialName("showAssets") val showAssets: Boolean = true,
|
||||
@SerialName("showNutrition") val showNutrition: Boolean = true,
|
||||
)
|
||||
|
||||
@Serializable
|
||||
data class AddRecipeNote(
|
||||
@SerialName("title") val title: String = "",
|
||||
@SerialName("text") val text: String = "",
|
||||
)
|
||||
|
||||
@Serializable
|
||||
data class AddRecipeInstruction(
|
||||
@SerialName("title") val title: String = "",
|
||||
@SerialName("text") val text: String = "",
|
||||
)
|
||||
|
||||
@Serializable
|
||||
data class AddRecipeIngredient(
|
||||
@SerialName("disableAmount") val disableAmount: Boolean = true,
|
||||
@SerialName("food") val food: String? = null,
|
||||
@SerialName("note") val note: String = "",
|
||||
@SerialName("quantity") val quantity: Int = 1,
|
||||
@SerialName("title") val title: String? = null,
|
||||
@SerialName("unit") val unit: String? = null,
|
||||
)
|
||||
@@ -0,0 +1,7 @@
|
||||
package gq.kirmanak.mealient.datasource.models
|
||||
|
||||
import kotlinx.serialization.SerialName
|
||||
import kotlinx.serialization.Serializable
|
||||
|
||||
@Serializable
|
||||
data class ErrorDetail(@SerialName("detail") val detail: String? = null)
|
||||
@@ -0,0 +1,14 @@
|
||||
package gq.kirmanak.mealient.datasource.models
|
||||
|
||||
import kotlinx.serialization.SerialName
|
||||
import kotlinx.serialization.Serializable
|
||||
|
||||
@Serializable
|
||||
data class GetRecipeIngredientResponse(
|
||||
@SerialName("title") val title: String = "",
|
||||
@SerialName("note") val note: String = "",
|
||||
@SerialName("unit") val unit: String = "",
|
||||
@SerialName("food") val food: String = "",
|
||||
@SerialName("disableAmount") val disableAmount: Boolean,
|
||||
@SerialName("quantity") val quantity: Int,
|
||||
)
|
||||
@@ -0,0 +1,10 @@
|
||||
package gq.kirmanak.mealient.datasource.models
|
||||
|
||||
import kotlinx.serialization.SerialName
|
||||
import kotlinx.serialization.Serializable
|
||||
|
||||
@Serializable
|
||||
data class GetRecipeInstructionResponse(
|
||||
@SerialName("title") val title: String = "",
|
||||
@SerialName("text") val text: String,
|
||||
)
|
||||
@@ -0,0 +1,23 @@
|
||||
package gq.kirmanak.mealient.datasource.models
|
||||
|
||||
import kotlinx.datetime.LocalDate
|
||||
import kotlinx.datetime.LocalDateTime
|
||||
import kotlinx.serialization.SerialName
|
||||
import kotlinx.serialization.Serializable
|
||||
|
||||
@Serializable
|
||||
data class GetRecipeResponse(
|
||||
@SerialName("id") val remoteId: Long,
|
||||
@SerialName("name") val name: String,
|
||||
@SerialName("slug") val slug: String,
|
||||
@SerialName("image") val image: String,
|
||||
@SerialName("description") val description: String = "",
|
||||
@SerialName("recipeCategory") val recipeCategories: List<String>,
|
||||
@SerialName("tags") val tags: List<String>,
|
||||
@SerialName("rating") val rating: Int?,
|
||||
@SerialName("dateAdded") val dateAdded: LocalDate,
|
||||
@SerialName("dateUpdated") val dateUpdated: LocalDateTime,
|
||||
@SerialName("recipeYield") val recipeYield: String = "",
|
||||
@SerialName("recipeIngredient") val recipeIngredients: List<GetRecipeIngredientResponse>,
|
||||
@SerialName("recipeInstructions") val recipeInstructions: List<GetRecipeInstructionResponse>,
|
||||
)
|
||||
@@ -0,0 +1,24 @@
|
||||
package gq.kirmanak.mealient.datasource.models
|
||||
|
||||
import kotlinx.datetime.LocalDate
|
||||
import kotlinx.datetime.LocalDateTime
|
||||
import kotlinx.serialization.SerialName
|
||||
import kotlinx.serialization.Serializable
|
||||
|
||||
@Serializable
|
||||
data class GetRecipeSummaryResponse(
|
||||
@SerialName("id") val remoteId: Long,
|
||||
@SerialName("name") val name: String,
|
||||
@SerialName("slug") val slug: String,
|
||||
@SerialName("image") val image: String?,
|
||||
@SerialName("description") val description: String = "",
|
||||
@SerialName("recipeCategory") val recipeCategories: List<String>,
|
||||
@SerialName("tags") val tags: List<String>,
|
||||
@SerialName("rating") val rating: Int?,
|
||||
@SerialName("dateAdded") val dateAdded: LocalDate,
|
||||
@SerialName("dateUpdated") val dateUpdated: LocalDateTime
|
||||
) {
|
||||
override fun toString(): String {
|
||||
return "GetRecipeSummaryResponse(remoteId=$remoteId, name='$name')"
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,7 @@
|
||||
package gq.kirmanak.mealient.datasource.models
|
||||
|
||||
import kotlinx.serialization.SerialName
|
||||
import kotlinx.serialization.Serializable
|
||||
|
||||
@Serializable
|
||||
data class GetTokenResponse(@SerialName("access_token") val accessToken: String)
|
||||
@@ -0,0 +1,8 @@
|
||||
package gq.kirmanak.mealient.datasource.models
|
||||
|
||||
sealed class NetworkError(cause: Throwable) : RuntimeException(cause) {
|
||||
class Unauthorized(cause: Throwable) : NetworkError(cause)
|
||||
class NoServerConnection(cause: Throwable) : NetworkError(cause)
|
||||
class NotMealie(cause: Throwable) : NetworkError(cause)
|
||||
class MalformedUrl(cause: Throwable) : NetworkError(cause)
|
||||
}
|
||||
@@ -0,0 +1,14 @@
|
||||
package gq.kirmanak.mealient.datasource.models
|
||||
|
||||
import kotlinx.serialization.SerialName
|
||||
import kotlinx.serialization.Serializable
|
||||
|
||||
@Serializable
|
||||
data class VersionResponse(
|
||||
@SerialName("production")
|
||||
val production: Boolean,
|
||||
@SerialName("version")
|
||||
val version: String,
|
||||
@SerialName("demoStatus")
|
||||
val demoStatus: Boolean,
|
||||
)
|
||||
@@ -0,0 +1,20 @@
|
||||
package gq.kirmanak.mealient
|
||||
|
||||
import dagger.Module
|
||||
import dagger.Provides
|
||||
import dagger.hilt.InstallIn
|
||||
import dagger.hilt.components.SingletonComponent
|
||||
import okhttp3.Interceptor
|
||||
import javax.inject.Singleton
|
||||
|
||||
@Module
|
||||
@InstallIn(SingletonComponent::class)
|
||||
object ReleaseModule {
|
||||
|
||||
// Release version of the application doesn't have any interceptors but this Set
|
||||
// is required by Dagger, so an empty Set is provided here
|
||||
// Use @JvmSuppressWildcards because otherwise dagger can't inject it (https://stackoverflow.com/a/43149382)
|
||||
@Provides
|
||||
@Singleton
|
||||
fun provideInterceptors(): Set<@JvmSuppressWildcards Interceptor> = emptySet()
|
||||
}
|
||||
Reference in New Issue
Block a user