Merge pull request #108 from kirmanak/share-recipe-url
Parse recipe from shared URL
This commit is contained in:
@@ -87,6 +87,8 @@ dependencies {
|
|||||||
implementation(libs.androidx.lifecycle.livedataKtx)
|
implementation(libs.androidx.lifecycle.livedataKtx)
|
||||||
implementation(libs.androidx.lifecycle.viewmodelKtx)
|
implementation(libs.androidx.lifecycle.viewmodelKtx)
|
||||||
|
|
||||||
|
implementation(libs.androidx.shareTarget)
|
||||||
|
|
||||||
implementation(libs.google.dagger.hiltAndroid)
|
implementation(libs.google.dagger.hiltAndroid)
|
||||||
kapt(libs.google.dagger.hiltCompiler)
|
kapt(libs.google.dagger.hiltCompiler)
|
||||||
kaptTest(libs.google.dagger.hiltAndroidCompiler)
|
kaptTest(libs.google.dagger.hiltAndroidCompiler)
|
||||||
|
|||||||
@@ -19,14 +19,27 @@
|
|||||||
tools:ignore="UnusedAttribute">
|
tools:ignore="UnusedAttribute">
|
||||||
<activity
|
<activity
|
||||||
android:name=".ui.activity.MainActivity"
|
android:name=".ui.activity.MainActivity"
|
||||||
android:windowSoftInputMode="adjustPan"
|
android:exported="true"
|
||||||
android:exported="true">
|
android:windowSoftInputMode="adjustPan">
|
||||||
<intent-filter>
|
<intent-filter>
|
||||||
<action android:name="android.intent.action.MAIN" />
|
<action android:name="android.intent.action.MAIN" />
|
||||||
|
|
||||||
<category android:name="android.intent.category.LAUNCHER" />
|
<category android:name="android.intent.category.LAUNCHER" />
|
||||||
</intent-filter>
|
</intent-filter>
|
||||||
</activity>
|
</activity>
|
||||||
|
<activity
|
||||||
|
android:name=".ui.share.ShareRecipeActivity"
|
||||||
|
android:exported="true"
|
||||||
|
android:theme="@style/AppTheme">
|
||||||
|
<intent-filter>
|
||||||
|
<action android:name="android.intent.action.SEND" />
|
||||||
|
<category android:name="android.intent.category.DEFAULT" />
|
||||||
|
<data android:mimeType="text/plain" />
|
||||||
|
</intent-filter>
|
||||||
|
<meta-data
|
||||||
|
android:name="android.service.chooser.chooser_target_service"
|
||||||
|
android:value="androidx.sharetarget.ChooserTargetServiceCompat" />
|
||||||
|
</activity>
|
||||||
</application>
|
</application>
|
||||||
|
|
||||||
</manifest>
|
</manifest>
|
||||||
@@ -8,11 +8,18 @@ import gq.kirmanak.mealient.data.baseurl.ServerVersion
|
|||||||
import gq.kirmanak.mealient.data.recipes.network.FullRecipeInfo
|
import gq.kirmanak.mealient.data.recipes.network.FullRecipeInfo
|
||||||
import gq.kirmanak.mealient.data.recipes.network.RecipeDataSource
|
import gq.kirmanak.mealient.data.recipes.network.RecipeDataSource
|
||||||
import gq.kirmanak.mealient.data.recipes.network.RecipeSummaryInfo
|
import gq.kirmanak.mealient.data.recipes.network.RecipeSummaryInfo
|
||||||
|
import gq.kirmanak.mealient.data.share.ParseRecipeDataSource
|
||||||
|
import gq.kirmanak.mealient.data.share.ParseRecipeURLInfo
|
||||||
import gq.kirmanak.mealient.datasource.NetworkError
|
import gq.kirmanak.mealient.datasource.NetworkError
|
||||||
import gq.kirmanak.mealient.datasource.runCatchingExceptCancel
|
import gq.kirmanak.mealient.datasource.runCatchingExceptCancel
|
||||||
import gq.kirmanak.mealient.datasource.v0.MealieDataSourceV0
|
import gq.kirmanak.mealient.datasource.v0.MealieDataSourceV0
|
||||||
import gq.kirmanak.mealient.datasource.v1.MealieDataSourceV1
|
import gq.kirmanak.mealient.datasource.v1.MealieDataSourceV1
|
||||||
import gq.kirmanak.mealient.extensions.*
|
import gq.kirmanak.mealient.extensions.toFullRecipeInfo
|
||||||
|
import gq.kirmanak.mealient.extensions.toRecipeSummaryInfo
|
||||||
|
import gq.kirmanak.mealient.extensions.toV0Request
|
||||||
|
import gq.kirmanak.mealient.extensions.toV1CreateRequest
|
||||||
|
import gq.kirmanak.mealient.extensions.toV1Request
|
||||||
|
import gq.kirmanak.mealient.extensions.toV1UpdateRequest
|
||||||
import gq.kirmanak.mealient.logging.Logger
|
import gq.kirmanak.mealient.logging.Logger
|
||||||
import javax.inject.Inject
|
import javax.inject.Inject
|
||||||
import javax.inject.Singleton
|
import javax.inject.Singleton
|
||||||
@@ -24,7 +31,7 @@ class MealieDataSourceWrapper @Inject constructor(
|
|||||||
private val v0Source: MealieDataSourceV0,
|
private val v0Source: MealieDataSourceV0,
|
||||||
private val v1Source: MealieDataSourceV1,
|
private val v1Source: MealieDataSourceV1,
|
||||||
private val logger: Logger,
|
private val logger: Logger,
|
||||||
) : AddRecipeDataSource, RecipeDataSource {
|
) : AddRecipeDataSource, RecipeDataSource, ParseRecipeDataSource {
|
||||||
|
|
||||||
override suspend fun addRecipe(
|
override suspend fun addRecipe(
|
||||||
recipe: AddRecipeInfo,
|
recipe: AddRecipeInfo,
|
||||||
@@ -64,6 +71,19 @@ class MealieDataSourceWrapper @Inject constructor(
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
override suspend fun parseRecipeFromURL(
|
||||||
|
parseRecipeURLInfo: ParseRecipeURLInfo,
|
||||||
|
): String = makeCall { token, url, version ->
|
||||||
|
when (version) {
|
||||||
|
ServerVersion.V0 -> {
|
||||||
|
v0Source.parseRecipeFromURL(url, token, parseRecipeURLInfo.toV0Request())
|
||||||
|
}
|
||||||
|
ServerVersion.V1 -> {
|
||||||
|
v1Source.parseRecipeFromURL(url, token, parseRecipeURLInfo.toV1Request())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
private suspend inline fun <T> makeCall(block: (String?, String, ServerVersion) -> T): T {
|
private suspend inline fun <T> makeCall(block: (String?, String, ServerVersion) -> T): T {
|
||||||
val authHeader = authRepo.getAuthHeader()
|
val authHeader = authRepo.getAuthHeader()
|
||||||
val url = serverInfoRepo.requireUrl()
|
val url = serverInfoRepo.requireUrl()
|
||||||
|
|||||||
@@ -0,0 +1,6 @@
|
|||||||
|
package gq.kirmanak.mealient.data.share
|
||||||
|
|
||||||
|
interface ParseRecipeDataSource {
|
||||||
|
|
||||||
|
suspend fun parseRecipeFromURL(parseRecipeURLInfo: ParseRecipeURLInfo): String
|
||||||
|
}
|
||||||
@@ -0,0 +1,6 @@
|
|||||||
|
package gq.kirmanak.mealient.data.share
|
||||||
|
|
||||||
|
data class ParseRecipeURLInfo(
|
||||||
|
val url: String,
|
||||||
|
val includeTags: Boolean
|
||||||
|
)
|
||||||
@@ -0,0 +1,6 @@
|
|||||||
|
package gq.kirmanak.mealient.data.share
|
||||||
|
|
||||||
|
interface ShareRecipeRepo {
|
||||||
|
|
||||||
|
suspend fun saveRecipeByURL(url: CharSequence): String
|
||||||
|
}
|
||||||
@@ -0,0 +1,22 @@
|
|||||||
|
package gq.kirmanak.mealient.data.share
|
||||||
|
|
||||||
|
import androidx.core.util.PatternsCompat
|
||||||
|
import gq.kirmanak.mealient.logging.Logger
|
||||||
|
import javax.inject.Inject
|
||||||
|
import javax.inject.Singleton
|
||||||
|
|
||||||
|
@Singleton
|
||||||
|
class ShareRecipeRepoImpl @Inject constructor(
|
||||||
|
private val logger: Logger,
|
||||||
|
private val parseRecipeDataSource: ParseRecipeDataSource,
|
||||||
|
) : ShareRecipeRepo {
|
||||||
|
|
||||||
|
override suspend fun saveRecipeByURL(url: CharSequence): String {
|
||||||
|
logger.v { "saveRecipeByURL() called with: url = $url" }
|
||||||
|
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)
|
||||||
|
return parseRecipeDataSource.parseRecipeFromURL(request)
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,24 @@
|
|||||||
|
package gq.kirmanak.mealient.di
|
||||||
|
|
||||||
|
import dagger.Binds
|
||||||
|
import dagger.Module
|
||||||
|
import dagger.hilt.InstallIn
|
||||||
|
import dagger.hilt.components.SingletonComponent
|
||||||
|
import gq.kirmanak.mealient.data.network.MealieDataSourceWrapper
|
||||||
|
import gq.kirmanak.mealient.data.share.ParseRecipeDataSource
|
||||||
|
import gq.kirmanak.mealient.data.share.ShareRecipeRepo
|
||||||
|
import gq.kirmanak.mealient.data.share.ShareRecipeRepoImpl
|
||||||
|
import javax.inject.Singleton
|
||||||
|
|
||||||
|
@Module
|
||||||
|
@InstallIn(SingletonComponent::class)
|
||||||
|
interface ShareRecipeModule {
|
||||||
|
|
||||||
|
@Binds
|
||||||
|
@Singleton
|
||||||
|
fun bindShareRecipeRepo(shareRecipeRepoImpl: ShareRecipeRepoImpl): ShareRecipeRepo
|
||||||
|
|
||||||
|
@Binds
|
||||||
|
@Singleton
|
||||||
|
fun bindParseRecipeDataSource(mealieDataSourceWrapper: MealieDataSourceWrapper): ParseRecipeDataSource
|
||||||
|
}
|
||||||
@@ -9,12 +9,32 @@ import gq.kirmanak.mealient.data.recipes.network.FullRecipeInfo
|
|||||||
import gq.kirmanak.mealient.data.recipes.network.RecipeIngredientInfo
|
import gq.kirmanak.mealient.data.recipes.network.RecipeIngredientInfo
|
||||||
import gq.kirmanak.mealient.data.recipes.network.RecipeInstructionInfo
|
import gq.kirmanak.mealient.data.recipes.network.RecipeInstructionInfo
|
||||||
import gq.kirmanak.mealient.data.recipes.network.RecipeSummaryInfo
|
import gq.kirmanak.mealient.data.recipes.network.RecipeSummaryInfo
|
||||||
|
import gq.kirmanak.mealient.data.share.ParseRecipeURLInfo
|
||||||
import gq.kirmanak.mealient.database.recipe.entity.RecipeEntity
|
import gq.kirmanak.mealient.database.recipe.entity.RecipeEntity
|
||||||
import gq.kirmanak.mealient.database.recipe.entity.RecipeIngredientEntity
|
import gq.kirmanak.mealient.database.recipe.entity.RecipeIngredientEntity
|
||||||
import gq.kirmanak.mealient.database.recipe.entity.RecipeInstructionEntity
|
import gq.kirmanak.mealient.database.recipe.entity.RecipeInstructionEntity
|
||||||
import gq.kirmanak.mealient.database.recipe.entity.RecipeSummaryEntity
|
import gq.kirmanak.mealient.database.recipe.entity.RecipeSummaryEntity
|
||||||
import gq.kirmanak.mealient.datasource.v0.models.*
|
import gq.kirmanak.mealient.datasource.v0.models.AddRecipeIngredientV0
|
||||||
import gq.kirmanak.mealient.datasource.v1.models.*
|
import gq.kirmanak.mealient.datasource.v0.models.AddRecipeInstructionV0
|
||||||
|
import gq.kirmanak.mealient.datasource.v0.models.AddRecipeRequestV0
|
||||||
|
import gq.kirmanak.mealient.datasource.v0.models.AddRecipeSettingsV0
|
||||||
|
import gq.kirmanak.mealient.datasource.v0.models.GetRecipeIngredientResponseV0
|
||||||
|
import gq.kirmanak.mealient.datasource.v0.models.GetRecipeInstructionResponseV0
|
||||||
|
import gq.kirmanak.mealient.datasource.v0.models.GetRecipeResponseV0
|
||||||
|
import gq.kirmanak.mealient.datasource.v0.models.GetRecipeSummaryResponseV0
|
||||||
|
import gq.kirmanak.mealient.datasource.v0.models.ParseRecipeURLRequestV0
|
||||||
|
import gq.kirmanak.mealient.datasource.v0.models.VersionResponseV0
|
||||||
|
import gq.kirmanak.mealient.datasource.v1.models.AddRecipeIngredientV1
|
||||||
|
import gq.kirmanak.mealient.datasource.v1.models.AddRecipeInstructionV1
|
||||||
|
import gq.kirmanak.mealient.datasource.v1.models.AddRecipeSettingsV1
|
||||||
|
import gq.kirmanak.mealient.datasource.v1.models.CreateRecipeRequestV1
|
||||||
|
import gq.kirmanak.mealient.datasource.v1.models.GetRecipeIngredientResponseV1
|
||||||
|
import gq.kirmanak.mealient.datasource.v1.models.GetRecipeInstructionResponseV1
|
||||||
|
import gq.kirmanak.mealient.datasource.v1.models.GetRecipeResponseV1
|
||||||
|
import gq.kirmanak.mealient.datasource.v1.models.GetRecipeSummaryResponseV1
|
||||||
|
import gq.kirmanak.mealient.datasource.v1.models.ParseRecipeURLRequestV1
|
||||||
|
import gq.kirmanak.mealient.datasource.v1.models.UpdateRecipeRequestV1
|
||||||
|
import gq.kirmanak.mealient.datasource.v1.models.VersionResponseV1
|
||||||
import gq.kirmanak.mealient.datastore.recipe.AddRecipeDraft
|
import gq.kirmanak.mealient.datastore.recipe.AddRecipeDraft
|
||||||
import java.util.*
|
import java.util.*
|
||||||
|
|
||||||
@@ -170,4 +190,13 @@ private fun AddRecipeInstructionInfo.toV1Instruction() = AddRecipeInstructionV1(
|
|||||||
id = UUID.randomUUID().toString(),
|
id = UUID.randomUUID().toString(),
|
||||||
text = text,
|
text = text,
|
||||||
ingredientReferences = emptyList(),
|
ingredientReferences = emptyList(),
|
||||||
)
|
)
|
||||||
|
|
||||||
|
fun ParseRecipeURLInfo.toV1Request() = ParseRecipeURLRequestV1(
|
||||||
|
url = url,
|
||||||
|
includeTags = includeTags,
|
||||||
|
)
|
||||||
|
|
||||||
|
fun ParseRecipeURLInfo.toV0Request() = ParseRecipeURLRequestV0(
|
||||||
|
url = url,
|
||||||
|
)
|
||||||
|
|||||||
36
app/src/main/java/gq/kirmanak/mealient/ui/BaseActivity.kt
Normal file
36
app/src/main/java/gq/kirmanak/mealient/ui/BaseActivity.kt
Normal file
@@ -0,0 +1,36 @@
|
|||||||
|
package gq.kirmanak.mealient.ui
|
||||||
|
|
||||||
|
import android.os.Bundle
|
||||||
|
import android.view.View
|
||||||
|
import androidx.annotation.IdRes
|
||||||
|
import androidx.annotation.LayoutRes
|
||||||
|
import androidx.appcompat.app.AppCompatActivity
|
||||||
|
import androidx.core.view.WindowInsetsControllerCompat
|
||||||
|
import androidx.viewbinding.ViewBinding
|
||||||
|
import by.kirich1409.viewbindingdelegate.viewBinding
|
||||||
|
import gq.kirmanak.mealient.extensions.isDarkThemeOn
|
||||||
|
import gq.kirmanak.mealient.logging.Logger
|
||||||
|
import javax.inject.Inject
|
||||||
|
|
||||||
|
abstract class BaseActivity<T : ViewBinding>(
|
||||||
|
binder: (View) -> T,
|
||||||
|
@IdRes containerId: Int,
|
||||||
|
@LayoutRes layoutRes: Int,
|
||||||
|
) : AppCompatActivity(layoutRes) {
|
||||||
|
|
||||||
|
protected val binding by viewBinding(binder, containerId)
|
||||||
|
|
||||||
|
@Inject
|
||||||
|
lateinit var logger: Logger
|
||||||
|
|
||||||
|
override fun onCreate(savedInstanceState: Bundle?) {
|
||||||
|
super.onCreate(savedInstanceState)
|
||||||
|
logger.v { "onCreate() called with: savedInstanceState = $savedInstanceState" }
|
||||||
|
setContentView(binding.root)
|
||||||
|
with(WindowInsetsControllerCompat(window, window.decorView)) {
|
||||||
|
val isAppearanceLightBars = !isDarkThemeOn()
|
||||||
|
isAppearanceLightNavigationBars = isAppearanceLightBars
|
||||||
|
isAppearanceLightStatusBars = isAppearanceLightBars
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -15,6 +15,9 @@ sealed class OperationUiState<T> {
|
|||||||
val isProgress: Boolean
|
val isProgress: Boolean
|
||||||
get() = this is Progress
|
get() = this is Progress
|
||||||
|
|
||||||
|
val isFailure: Boolean
|
||||||
|
get() = this is Failure
|
||||||
|
|
||||||
fun updateButtonState(button: Button) {
|
fun updateButtonState(button: Button) {
|
||||||
button.isEnabled = !isProgress
|
button.isEnabled = !isProgress
|
||||||
button.isClickable = !isProgress
|
button.isClickable = !isProgress
|
||||||
@@ -24,9 +27,29 @@ sealed class OperationUiState<T> {
|
|||||||
progressBar.isVisible = isProgress
|
progressBar.isVisible = isProgress
|
||||||
}
|
}
|
||||||
|
|
||||||
class Initial<T> : OperationUiState<T>()
|
class Initial<T> : OperationUiState<T>() {
|
||||||
|
override fun equals(other: Any?): Boolean {
|
||||||
|
if (this === other) return true
|
||||||
|
if (javaClass != other?.javaClass) return false
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
|
||||||
class Progress<T> : OperationUiState<T>()
|
override fun hashCode(): Int {
|
||||||
|
return javaClass.hashCode()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
class Progress<T> : OperationUiState<T>() {
|
||||||
|
override fun equals(other: Any?): Boolean {
|
||||||
|
if (this === other) return true
|
||||||
|
if (javaClass != other?.javaClass) return false
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun hashCode(): Int {
|
||||||
|
return javaClass.hashCode()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
data class Failure<T>(val exception: Throwable) : OperationUiState<T>()
|
data class Failure<T>(val exception: Throwable) : OperationUiState<T>()
|
||||||
|
|
||||||
|
|||||||
@@ -3,16 +3,13 @@ package gq.kirmanak.mealient.ui.activity
|
|||||||
import android.os.Bundle
|
import android.os.Bundle
|
||||||
import android.view.MenuItem
|
import android.view.MenuItem
|
||||||
import androidx.activity.viewModels
|
import androidx.activity.viewModels
|
||||||
import androidx.appcompat.app.AppCompatActivity
|
|
||||||
import androidx.core.splashscreen.SplashScreen.Companion.installSplashScreen
|
import androidx.core.splashscreen.SplashScreen.Companion.installSplashScreen
|
||||||
import androidx.core.view.WindowInsetsControllerCompat
|
|
||||||
import androidx.core.view.isVisible
|
import androidx.core.view.isVisible
|
||||||
import androidx.core.view.iterator
|
import androidx.core.view.iterator
|
||||||
import androidx.drawerlayout.widget.DrawerLayout
|
import androidx.drawerlayout.widget.DrawerLayout
|
||||||
import androidx.navigation.NavController
|
import androidx.navigation.NavController
|
||||||
import androidx.navigation.NavDirections
|
import androidx.navigation.NavDirections
|
||||||
import androidx.navigation.fragment.NavHostFragment
|
import androidx.navigation.fragment.NavHostFragment
|
||||||
import by.kirich1409.viewbindingdelegate.viewBinding
|
|
||||||
import dagger.hilt.android.AndroidEntryPoint
|
import dagger.hilt.android.AndroidEntryPoint
|
||||||
import gq.kirmanak.mealient.NavGraphDirections.Companion.actionGlobalAddRecipeFragment
|
import gq.kirmanak.mealient.NavGraphDirections.Companion.actionGlobalAddRecipeFragment
|
||||||
import gq.kirmanak.mealient.NavGraphDirections.Companion.actionGlobalAuthenticationFragment
|
import gq.kirmanak.mealient.NavGraphDirections.Companion.actionGlobalAuthenticationFragment
|
||||||
@@ -21,28 +18,24 @@ import gq.kirmanak.mealient.NavGraphDirections.Companion.actionGlobalRecipesList
|
|||||||
import gq.kirmanak.mealient.R
|
import gq.kirmanak.mealient.R
|
||||||
import gq.kirmanak.mealient.databinding.MainActivityBinding
|
import gq.kirmanak.mealient.databinding.MainActivityBinding
|
||||||
import gq.kirmanak.mealient.extensions.collectWhenResumed
|
import gq.kirmanak.mealient.extensions.collectWhenResumed
|
||||||
import gq.kirmanak.mealient.extensions.isDarkThemeOn
|
|
||||||
import gq.kirmanak.mealient.extensions.observeOnce
|
import gq.kirmanak.mealient.extensions.observeOnce
|
||||||
import gq.kirmanak.mealient.logging.Logger
|
import gq.kirmanak.mealient.ui.BaseActivity
|
||||||
import javax.inject.Inject
|
|
||||||
|
|
||||||
@AndroidEntryPoint
|
@AndroidEntryPoint
|
||||||
class MainActivity : AppCompatActivity(R.layout.main_activity) {
|
class MainActivity : BaseActivity<MainActivityBinding>(
|
||||||
|
binder = MainActivityBinding::bind,
|
||||||
|
containerId = R.id.drawer,
|
||||||
|
layoutRes = R.layout.main_activity,
|
||||||
|
) {
|
||||||
|
|
||||||
private val binding: MainActivityBinding by viewBinding(MainActivityBinding::bind, R.id.drawer)
|
|
||||||
private val viewModel by viewModels<MainActivityViewModel>()
|
private val viewModel by viewModels<MainActivityViewModel>()
|
||||||
private val navController: NavController
|
private val navController: NavController
|
||||||
get() = binding.navHost.getFragment<NavHostFragment>().navController
|
get() = binding.navHost.getFragment<NavHostFragment>().navController
|
||||||
|
|
||||||
@Inject
|
|
||||||
lateinit var logger: Logger
|
|
||||||
|
|
||||||
override fun onCreate(savedInstanceState: Bundle?) {
|
override fun onCreate(savedInstanceState: Bundle?) {
|
||||||
val splashScreen = installSplashScreen()
|
val splashScreen = installSplashScreen()
|
||||||
super.onCreate(savedInstanceState)
|
super.onCreate(savedInstanceState)
|
||||||
logger.v { "onCreate() called with: savedInstanceState = $savedInstanceState" }
|
|
||||||
splashScreen.setKeepOnScreenCondition { viewModel.startDestination.value == null }
|
splashScreen.setKeepOnScreenCondition { viewModel.startDestination.value == null }
|
||||||
setContentView(binding.root)
|
|
||||||
setupUi()
|
setupUi()
|
||||||
configureNavGraph()
|
configureNavGraph()
|
||||||
}
|
}
|
||||||
@@ -67,11 +60,6 @@ class MainActivity : AppCompatActivity(R.layout.main_activity) {
|
|||||||
viewModel.onSearchQuery(query.trim().takeUnless { it.isEmpty() })
|
viewModel.onSearchQuery(query.trim().takeUnless { it.isEmpty() })
|
||||||
}
|
}
|
||||||
binding.navigationView.setNavigationItemSelectedListener(::onNavigationItemSelected)
|
binding.navigationView.setNavigationItemSelectedListener(::onNavigationItemSelected)
|
||||||
with(WindowInsetsControllerCompat(window, window.decorView)) {
|
|
||||||
val isAppearanceLightBars = !isDarkThemeOn()
|
|
||||||
isAppearanceLightNavigationBars = isAppearanceLightBars
|
|
||||||
isAppearanceLightStatusBars = isAppearanceLightBars
|
|
||||||
}
|
|
||||||
viewModel.uiStateLive.observe(this, ::onUiStateChange)
|
viewModel.uiStateLive.observe(this, ::onUiStateChange)
|
||||||
collectWhenResumed(viewModel.clearSearchViewFocus) {
|
collectWhenResumed(viewModel.clearSearchViewFocus) {
|
||||||
logger.d { "clearSearchViewFocus(): received event" }
|
logger.d { "clearSearchViewFocus(): received event" }
|
||||||
|
|||||||
@@ -0,0 +1,93 @@
|
|||||||
|
package gq.kirmanak.mealient.ui.share
|
||||||
|
|
||||||
|
import android.content.Intent
|
||||||
|
import android.graphics.drawable.Animatable2
|
||||||
|
import android.graphics.drawable.AnimatedVectorDrawable
|
||||||
|
import android.graphics.drawable.Drawable
|
||||||
|
import android.os.Bundle
|
||||||
|
import androidx.activity.viewModels
|
||||||
|
import androidx.core.view.isInvisible
|
||||||
|
import androidx.core.view.postDelayed
|
||||||
|
import dagger.hilt.android.AndroidEntryPoint
|
||||||
|
import gq.kirmanak.mealient.R
|
||||||
|
import gq.kirmanak.mealient.databinding.ActivityShareRecipeBinding
|
||||||
|
import gq.kirmanak.mealient.extensions.showLongToast
|
||||||
|
import gq.kirmanak.mealient.ui.BaseActivity
|
||||||
|
import gq.kirmanak.mealient.ui.OperationUiState
|
||||||
|
|
||||||
|
@AndroidEntryPoint
|
||||||
|
class ShareRecipeActivity : BaseActivity<ActivityShareRecipeBinding>(
|
||||||
|
binder = ActivityShareRecipeBinding::bind,
|
||||||
|
containerId = R.id.root,
|
||||||
|
layoutRes = R.layout.activity_share_recipe,
|
||||||
|
) {
|
||||||
|
|
||||||
|
private val viewModel: ShareRecipeViewModel by viewModels()
|
||||||
|
|
||||||
|
override fun onCreate(savedInstanceState: Bundle?) {
|
||||||
|
super.onCreate(savedInstanceState)
|
||||||
|
|
||||||
|
if (intent.action != Intent.ACTION_SEND || intent.type != "text/plain") {
|
||||||
|
logger.w { "onCreate: intent.action = ${intent.action}, intent.type = ${intent.type}" }
|
||||||
|
finish()
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
val url: CharSequence? = intent.getCharSequenceExtra(Intent.EXTRA_TEXT)
|
||||||
|
if (url == null) {
|
||||||
|
logger.w { "onCreate: Intent's EXTRA_TEXT was null" }
|
||||||
|
finish()
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
restartAnimationOnEnd()
|
||||||
|
viewModel.saveResult.observe(this, ::onStateUpdate)
|
||||||
|
viewModel.saveRecipeByURL(url)
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun onStateUpdate(state: OperationUiState<String>) {
|
||||||
|
binding.progress.isInvisible = !state.isProgress
|
||||||
|
withAnimatedDrawable {
|
||||||
|
if (state.isProgress) start() else stop()
|
||||||
|
}
|
||||||
|
if (state.isSuccess || state.isFailure) {
|
||||||
|
showLongToast(
|
||||||
|
if (state.isSuccess) R.string.activity_share_recipe_success_toast
|
||||||
|
else R.string.activity_share_recipe_failure_toast
|
||||||
|
)
|
||||||
|
finish()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun restartAnimationOnEnd() {
|
||||||
|
withAnimatedDrawable {
|
||||||
|
onAnimationEnd {
|
||||||
|
if (viewModel.saveResult.value?.isProgress == true) {
|
||||||
|
binding.progress.postDelayed(250) { start() }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private inline fun withAnimatedDrawable(block: AnimatedVectorDrawable.() -> Unit) {
|
||||||
|
binding.progress.drawable.let { drawable ->
|
||||||
|
if (drawable is AnimatedVectorDrawable) {
|
||||||
|
drawable.block()
|
||||||
|
} else {
|
||||||
|
logger.w { "withAnimatedDrawable: progress's drawable is not AnimatedVectorDrawable" }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private inline fun AnimatedVectorDrawable.onAnimationEnd(
|
||||||
|
crossinline block: AnimatedVectorDrawable.() -> Unit,
|
||||||
|
): Animatable2.AnimationCallback {
|
||||||
|
val callback = object : Animatable2.AnimationCallback() {
|
||||||
|
override fun onAnimationEnd(drawable: Drawable?) {
|
||||||
|
block()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
registerAnimationCallback(callback)
|
||||||
|
return callback
|
||||||
|
}
|
||||||
@@ -0,0 +1,34 @@
|
|||||||
|
package gq.kirmanak.mealient.ui.share
|
||||||
|
|
||||||
|
import androidx.lifecycle.LiveData
|
||||||
|
import androidx.lifecycle.MutableLiveData
|
||||||
|
import androidx.lifecycle.ViewModel
|
||||||
|
import androidx.lifecycle.viewModelScope
|
||||||
|
import dagger.hilt.android.lifecycle.HiltViewModel
|
||||||
|
import gq.kirmanak.mealient.data.share.ShareRecipeRepo
|
||||||
|
import gq.kirmanak.mealient.datasource.runCatchingExceptCancel
|
||||||
|
import gq.kirmanak.mealient.logging.Logger
|
||||||
|
import gq.kirmanak.mealient.ui.OperationUiState
|
||||||
|
import kotlinx.coroutines.launch
|
||||||
|
import javax.inject.Inject
|
||||||
|
|
||||||
|
@HiltViewModel
|
||||||
|
class ShareRecipeViewModel @Inject constructor(
|
||||||
|
private val shareRecipeRepo: ShareRecipeRepo,
|
||||||
|
private val logger: Logger,
|
||||||
|
) : ViewModel() {
|
||||||
|
|
||||||
|
private val _saveResult = MutableLiveData<OperationUiState<String>>(OperationUiState.Initial())
|
||||||
|
val saveResult: LiveData<OperationUiState<String>> get() = _saveResult
|
||||||
|
|
||||||
|
fun saveRecipeByURL(url: CharSequence) {
|
||||||
|
logger.v { "saveRecipeByURL() called with: url = $url" }
|
||||||
|
_saveResult.postValue(OperationUiState.Progress())
|
||||||
|
viewModelScope.launch {
|
||||||
|
val result = runCatchingExceptCancel { shareRecipeRepo.saveRecipeByURL(url) }
|
||||||
|
.onSuccess { logger.d { "Successfully saved recipe by URL" } }
|
||||||
|
.onFailure { logger.e(it) { "Can't save recipe by URL" } }
|
||||||
|
_saveResult.postValue(OperationUiState.fromResult(result))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
19
app/src/main/res/layout/activity_share_recipe.xml
Normal file
19
app/src/main/res/layout/activity_share_recipe.xml
Normal file
@@ -0,0 +1,19 @@
|
|||||||
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
|
<androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
|
||||||
|
xmlns:app="http://schemas.android.com/apk/res-auto"
|
||||||
|
android:id="@+id/root"
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="match_parent">
|
||||||
|
|
||||||
|
<ImageView
|
||||||
|
android:id="@+id/progress"
|
||||||
|
android:layout_width="wrap_content"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:contentDescription="@string/content_description_activity_share_recipe_progress"
|
||||||
|
app:layout_constraintBottom_toBottomOf="parent"
|
||||||
|
app:layout_constraintEnd_toEndOf="parent"
|
||||||
|
app:layout_constraintStart_toStartOf="parent"
|
||||||
|
app:layout_constraintTop_toTopOf="parent"
|
||||||
|
app:srcCompat="@drawable/ic_progress_bar" />
|
||||||
|
|
||||||
|
</androidx.constraintlayout.widget.ConstraintLayout>
|
||||||
@@ -51,4 +51,7 @@
|
|||||||
<string name="search_recipes_hint">Найти рецепты</string>
|
<string name="search_recipes_hint">Найти рецепты</string>
|
||||||
<string name="view_toolbar_navigation_icon_content_description">Открыть меню навигации</string>
|
<string name="view_toolbar_navigation_icon_content_description">Открыть меню навигации</string>
|
||||||
<string name="fragment_recipes_list_no_recipes">Нет рецептов</string>
|
<string name="fragment_recipes_list_no_recipes">Нет рецептов</string>
|
||||||
|
<string name="activity_share_recipe_success_toast">Рецепт успешно сохранен.</string>
|
||||||
|
<string name="activity_share_recipe_failure_toast">Что-то пошло не так.</string>
|
||||||
|
<string name="content_description_activity_share_recipe_progress">Индикатор прогресса</string>
|
||||||
</resources>
|
</resources>
|
||||||
4
app/src/main/res/values-v31/drawable.xml
Normal file
4
app/src/main/res/values-v31/drawable.xml
Normal file
@@ -0,0 +1,4 @@
|
|||||||
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
|
<resources>
|
||||||
|
<drawable name="ic_splash_screen">@drawable/ic_progress_bar</drawable>
|
||||||
|
</resources>
|
||||||
@@ -54,4 +54,7 @@
|
|||||||
<string name="menu_navigation_drawer_header" translatable="false">@string/app_name</string>
|
<string name="menu_navigation_drawer_header" translatable="false">@string/app_name</string>
|
||||||
<string name="view_toolbar_navigation_icon_content_description">Open navigation drawer</string>
|
<string name="view_toolbar_navigation_icon_content_description">Open navigation drawer</string>
|
||||||
<string name="fragment_recipes_list_no_recipes">No recipes</string>
|
<string name="fragment_recipes_list_no_recipes">No recipes</string>
|
||||||
|
<string name="activity_share_recipe_success_toast">Recipe saved successfully.</string>
|
||||||
|
<string name="activity_share_recipe_failure_toast">Something went wrong.</string>
|
||||||
|
<string name="content_description_activity_share_recipe_progress">Progress indicator</string>
|
||||||
</resources>
|
</resources>
|
||||||
@@ -0,0 +1,70 @@
|
|||||||
|
package gq.kirmanak.mealient.data.share
|
||||||
|
|
||||||
|
import gq.kirmanak.mealient.test.BaseUnitTest
|
||||||
|
import io.mockk.coEvery
|
||||||
|
import io.mockk.coVerify
|
||||||
|
import io.mockk.impl.annotations.MockK
|
||||||
|
import kotlinx.coroutines.ExperimentalCoroutinesApi
|
||||||
|
import kotlinx.coroutines.test.runTest
|
||||||
|
import org.junit.Test
|
||||||
|
|
||||||
|
@OptIn(ExperimentalCoroutinesApi::class)
|
||||||
|
class ShareRecipeRepoImplTest : BaseUnitTest() {
|
||||||
|
|
||||||
|
|
||||||
|
@MockK(relaxUnitFun = true)
|
||||||
|
lateinit var parseRecipeDataSource: ParseRecipeDataSource
|
||||||
|
|
||||||
|
lateinit var subject: ShareRecipeRepo
|
||||||
|
|
||||||
|
override fun setUp() {
|
||||||
|
super.setUp()
|
||||||
|
subject = ShareRecipeRepoImpl(logger, parseRecipeDataSource)
|
||||||
|
coEvery { parseRecipeDataSource.parseRecipeFromURL(any()) } returns ""
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test(expected = IllegalArgumentException::class)
|
||||||
|
fun `when url is empty expect saveRecipeByURL throws Exception`() = runTest {
|
||||||
|
subject.saveRecipeByURL("")
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun `when url is correct expect saveRecipeByURL saves it`() = runTest {
|
||||||
|
subject.saveRecipeByURL("https://www.allrecipes.com/recipe/215447/dads-leftover-turkey-pot-pie/")
|
||||||
|
val expected = ParseRecipeURLInfo(
|
||||||
|
url = "https://www.allrecipes.com/recipe/215447/dads-leftover-turkey-pot-pie/",
|
||||||
|
includeTags = true
|
||||||
|
)
|
||||||
|
coVerify { parseRecipeDataSource.parseRecipeFromURL(eq(expected)) }
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun `when url has prefix expect saveRecipeByURL removes it`() = runTest {
|
||||||
|
subject.saveRecipeByURL("My favorite recipe: https://www.allrecipes.com/recipe/215447/dads-leftover-turkey-pot-pie/")
|
||||||
|
val expected = ParseRecipeURLInfo(
|
||||||
|
url = "https://www.allrecipes.com/recipe/215447/dads-leftover-turkey-pot-pie/",
|
||||||
|
includeTags = true
|
||||||
|
)
|
||||||
|
coVerify { parseRecipeDataSource.parseRecipeFromURL(eq(expected)) }
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun `when url has suffix expect saveRecipeByURL removes it`() = runTest {
|
||||||
|
subject.saveRecipeByURL("https://www.allrecipes.com/recipe/215447/dads-leftover-turkey-pot-pie/ is my favorite recipe")
|
||||||
|
val expected = ParseRecipeURLInfo(
|
||||||
|
url = "https://www.allrecipes.com/recipe/215447/dads-leftover-turkey-pot-pie",
|
||||||
|
includeTags = true
|
||||||
|
)
|
||||||
|
coVerify { parseRecipeDataSource.parseRecipeFromURL(eq(expected)) }
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun `when url has prefix and suffix expect saveRecipeByURL removes them`() = runTest {
|
||||||
|
subject.saveRecipeByURL("Actually, https://www.allrecipes.com/recipe/215447/dads-leftover-turkey-pot-pie/ is my favorite recipe")
|
||||||
|
val expected = ParseRecipeURLInfo(
|
||||||
|
url = "https://www.allrecipes.com/recipe/215447/dads-leftover-turkey-pot-pie",
|
||||||
|
includeTags = true
|
||||||
|
)
|
||||||
|
coVerify { parseRecipeDataSource.parseRecipeFromURL(eq(expected)) }
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,70 @@
|
|||||||
|
package gq.kirmanak.mealient.ui.share
|
||||||
|
|
||||||
|
import androidx.lifecycle.asFlow
|
||||||
|
import com.google.common.truth.Truth.assertThat
|
||||||
|
import gq.kirmanak.mealient.data.share.ShareRecipeRepo
|
||||||
|
import gq.kirmanak.mealient.test.BaseUnitTest
|
||||||
|
import gq.kirmanak.mealient.ui.OperationUiState
|
||||||
|
import io.mockk.coEvery
|
||||||
|
import io.mockk.coVerify
|
||||||
|
import io.mockk.impl.annotations.MockK
|
||||||
|
import kotlinx.coroutines.Dispatchers
|
||||||
|
import kotlinx.coroutines.ExperimentalCoroutinesApi
|
||||||
|
import kotlinx.coroutines.async
|
||||||
|
import kotlinx.coroutines.flow.take
|
||||||
|
import kotlinx.coroutines.flow.toList
|
||||||
|
import kotlinx.coroutines.test.runTest
|
||||||
|
import org.junit.Before
|
||||||
|
import org.junit.Rule
|
||||||
|
import org.junit.Test
|
||||||
|
import org.junit.rules.Timeout
|
||||||
|
|
||||||
|
@OptIn(ExperimentalCoroutinesApi::class)
|
||||||
|
class ShareRecipeViewModelTest : BaseUnitTest() {
|
||||||
|
|
||||||
|
@MockK(relaxUnitFun = true)
|
||||||
|
lateinit var shareRecipeRepo: ShareRecipeRepo
|
||||||
|
|
||||||
|
lateinit var subject: ShareRecipeViewModel
|
||||||
|
|
||||||
|
@get:Rule
|
||||||
|
val timeoutRule: Timeout = Timeout.seconds(5)
|
||||||
|
|
||||||
|
@Before
|
||||||
|
override fun setUp() {
|
||||||
|
super.setUp()
|
||||||
|
subject = ShareRecipeViewModel(
|
||||||
|
shareRecipeRepo = shareRecipeRepo,
|
||||||
|
logger = logger,
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun `when repo throws expect saveRecipeByURL to update saveResult`() {
|
||||||
|
coEvery { shareRecipeRepo.saveRecipeByURL(any()) } throws RuntimeException()
|
||||||
|
subject.saveRecipeByURL("")
|
||||||
|
assertThat(subject.saveResult.value).isInstanceOf(OperationUiState.Failure::class.java)
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun `when repo returns result expect saveResult to show progress before result`() = runTest {
|
||||||
|
val deferredActual = async(Dispatchers.Default) {
|
||||||
|
subject.saveResult.asFlow().take(3).toList(mutableListOf())
|
||||||
|
}
|
||||||
|
coEvery { shareRecipeRepo.saveRecipeByURL(any()) } returns "result"
|
||||||
|
subject.saveRecipeByURL("")
|
||||||
|
val actual = deferredActual.await()
|
||||||
|
assertThat(actual).containsExactly(
|
||||||
|
OperationUiState.Initial<String>(),
|
||||||
|
OperationUiState.Progress<String>(),
|
||||||
|
OperationUiState.Success("result"),
|
||||||
|
).inOrder()
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun `when url is given expect saveRecipeByURL to pass it to repo`() = runTest {
|
||||||
|
coEvery { shareRecipeRepo.saveRecipeByURL(any()) } returns "result"
|
||||||
|
subject.saveRecipeByURL("https://www.allrecipes.com/recipe/215447/dads-leftover-turkey-pot-pie/")
|
||||||
|
coVerify { shareRecipeRepo.saveRecipeByURL(eq("https://www.allrecipes.com/recipe/215447/dads-leftover-turkey-pot-pie/")) }
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -3,6 +3,7 @@ package gq.kirmanak.mealient.datasource.v0
|
|||||||
import gq.kirmanak.mealient.datasource.v0.models.AddRecipeRequestV0
|
import gq.kirmanak.mealient.datasource.v0.models.AddRecipeRequestV0
|
||||||
import gq.kirmanak.mealient.datasource.v0.models.GetRecipeResponseV0
|
import gq.kirmanak.mealient.datasource.v0.models.GetRecipeResponseV0
|
||||||
import gq.kirmanak.mealient.datasource.v0.models.GetRecipeSummaryResponseV0
|
import gq.kirmanak.mealient.datasource.v0.models.GetRecipeSummaryResponseV0
|
||||||
|
import gq.kirmanak.mealient.datasource.v0.models.ParseRecipeURLRequestV0
|
||||||
import gq.kirmanak.mealient.datasource.v0.models.VersionResponseV0
|
import gq.kirmanak.mealient.datasource.v0.models.VersionResponseV0
|
||||||
|
|
||||||
interface MealieDataSourceV0 {
|
interface MealieDataSourceV0 {
|
||||||
@@ -38,4 +39,10 @@ interface MealieDataSourceV0 {
|
|||||||
token: String?,
|
token: String?,
|
||||||
slug: String,
|
slug: String,
|
||||||
): GetRecipeResponseV0
|
): GetRecipeResponseV0
|
||||||
|
|
||||||
|
suspend fun parseRecipeFromURL(
|
||||||
|
baseUrl: String,
|
||||||
|
token: String?,
|
||||||
|
request: ParseRecipeURLRequestV0,
|
||||||
|
): String
|
||||||
}
|
}
|
||||||
@@ -3,7 +3,12 @@ package gq.kirmanak.mealient.datasource.v0
|
|||||||
import gq.kirmanak.mealient.datasource.NetworkError
|
import gq.kirmanak.mealient.datasource.NetworkError
|
||||||
import gq.kirmanak.mealient.datasource.NetworkRequestWrapper
|
import gq.kirmanak.mealient.datasource.NetworkRequestWrapper
|
||||||
import gq.kirmanak.mealient.datasource.decode
|
import gq.kirmanak.mealient.datasource.decode
|
||||||
import gq.kirmanak.mealient.datasource.v0.models.*
|
import gq.kirmanak.mealient.datasource.v0.models.AddRecipeRequestV0
|
||||||
|
import gq.kirmanak.mealient.datasource.v0.models.ErrorDetailV0
|
||||||
|
import gq.kirmanak.mealient.datasource.v0.models.GetRecipeResponseV0
|
||||||
|
import gq.kirmanak.mealient.datasource.v0.models.GetRecipeSummaryResponseV0
|
||||||
|
import gq.kirmanak.mealient.datasource.v0.models.ParseRecipeURLRequestV0
|
||||||
|
import gq.kirmanak.mealient.datasource.v0.models.VersionResponseV0
|
||||||
import kotlinx.serialization.SerializationException
|
import kotlinx.serialization.SerializationException
|
||||||
import kotlinx.serialization.json.Json
|
import kotlinx.serialization.json.Json
|
||||||
import retrofit2.HttpException
|
import retrofit2.HttpException
|
||||||
@@ -77,4 +82,15 @@ class MealieDataSourceV0Impl @Inject constructor(
|
|||||||
logMethod = { "requestRecipeInfo" },
|
logMethod = { "requestRecipeInfo" },
|
||||||
logParameters = { "baseUrl = $baseUrl, token = $token, slug = $slug" }
|
logParameters = { "baseUrl = $baseUrl, token = $token, slug = $slug" }
|
||||||
)
|
)
|
||||||
|
|
||||||
|
override suspend fun parseRecipeFromURL(
|
||||||
|
baseUrl: String,
|
||||||
|
token: String?,
|
||||||
|
request: ParseRecipeURLRequestV0
|
||||||
|
): String = networkRequestWrapper.makeCallAndHandleUnauthorized(
|
||||||
|
block = { service.createRecipeFromURL("$baseUrl/api/recipes/create-url", token, request) },
|
||||||
|
logMethod = { "parseRecipeFromURL" },
|
||||||
|
logParameters = { "baseUrl = $baseUrl, token = $token, request = $request" }
|
||||||
|
|
||||||
|
)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -39,4 +39,11 @@ interface MealieServiceV0 {
|
|||||||
@Url url: String,
|
@Url url: String,
|
||||||
@Header(AUTHORIZATION_HEADER_NAME) token: String?,
|
@Header(AUTHORIZATION_HEADER_NAME) token: String?,
|
||||||
): GetRecipeResponseV0
|
): GetRecipeResponseV0
|
||||||
|
|
||||||
|
@POST
|
||||||
|
suspend fun createRecipeFromURL(
|
||||||
|
@Url url: String,
|
||||||
|
@Header(AUTHORIZATION_HEADER_NAME) token: String?,
|
||||||
|
@Body request: ParseRecipeURLRequestV0,
|
||||||
|
): String
|
||||||
}
|
}
|
||||||
@@ -0,0 +1,9 @@
|
|||||||
|
package gq.kirmanak.mealient.datasource.v0.models
|
||||||
|
|
||||||
|
import kotlinx.serialization.SerialName
|
||||||
|
import kotlinx.serialization.Serializable
|
||||||
|
|
||||||
|
@Serializable
|
||||||
|
data class ParseRecipeURLRequestV0(
|
||||||
|
@SerialName("url") val url: String,
|
||||||
|
)
|
||||||
@@ -1,6 +1,11 @@
|
|||||||
package gq.kirmanak.mealient.datasource.v1
|
package gq.kirmanak.mealient.datasource.v1
|
||||||
|
|
||||||
import gq.kirmanak.mealient.datasource.v1.models.*
|
import gq.kirmanak.mealient.datasource.v1.models.CreateRecipeRequestV1
|
||||||
|
import gq.kirmanak.mealient.datasource.v1.models.GetRecipeResponseV1
|
||||||
|
import gq.kirmanak.mealient.datasource.v1.models.GetRecipeSummaryResponseV1
|
||||||
|
import gq.kirmanak.mealient.datasource.v1.models.ParseRecipeURLRequestV1
|
||||||
|
import gq.kirmanak.mealient.datasource.v1.models.UpdateRecipeRequestV1
|
||||||
|
import gq.kirmanak.mealient.datasource.v1.models.VersionResponseV1
|
||||||
|
|
||||||
interface MealieDataSourceV1 {
|
interface MealieDataSourceV1 {
|
||||||
|
|
||||||
@@ -42,4 +47,10 @@ interface MealieDataSourceV1 {
|
|||||||
token: String?,
|
token: String?,
|
||||||
slug: String,
|
slug: String,
|
||||||
): GetRecipeResponseV1
|
): GetRecipeResponseV1
|
||||||
|
|
||||||
|
suspend fun parseRecipeFromURL(
|
||||||
|
baseUrl: String,
|
||||||
|
token: String?,
|
||||||
|
request: ParseRecipeURLRequestV1,
|
||||||
|
): String
|
||||||
}
|
}
|
||||||
@@ -3,7 +3,13 @@ package gq.kirmanak.mealient.datasource.v1
|
|||||||
import gq.kirmanak.mealient.datasource.NetworkError
|
import gq.kirmanak.mealient.datasource.NetworkError
|
||||||
import gq.kirmanak.mealient.datasource.NetworkRequestWrapper
|
import gq.kirmanak.mealient.datasource.NetworkRequestWrapper
|
||||||
import gq.kirmanak.mealient.datasource.decode
|
import gq.kirmanak.mealient.datasource.decode
|
||||||
import gq.kirmanak.mealient.datasource.v1.models.*
|
import gq.kirmanak.mealient.datasource.v1.models.CreateRecipeRequestV1
|
||||||
|
import gq.kirmanak.mealient.datasource.v1.models.ErrorDetailV1
|
||||||
|
import gq.kirmanak.mealient.datasource.v1.models.GetRecipeResponseV1
|
||||||
|
import gq.kirmanak.mealient.datasource.v1.models.GetRecipeSummaryResponseV1
|
||||||
|
import gq.kirmanak.mealient.datasource.v1.models.ParseRecipeURLRequestV1
|
||||||
|
import gq.kirmanak.mealient.datasource.v1.models.UpdateRecipeRequestV1
|
||||||
|
import gq.kirmanak.mealient.datasource.v1.models.VersionResponseV1
|
||||||
import kotlinx.serialization.SerializationException
|
import kotlinx.serialization.SerializationException
|
||||||
import kotlinx.serialization.json.Json
|
import kotlinx.serialization.json.Json
|
||||||
import retrofit2.HttpException
|
import retrofit2.HttpException
|
||||||
@@ -89,5 +95,16 @@ class MealieDataSourceV1Impl @Inject constructor(
|
|||||||
logParameters = { "baseUrl = $baseUrl, token = $token, slug = $slug" }
|
logParameters = { "baseUrl = $baseUrl, token = $token, slug = $slug" }
|
||||||
)
|
)
|
||||||
|
|
||||||
|
override suspend fun parseRecipeFromURL(
|
||||||
|
baseUrl: String,
|
||||||
|
token: String?,
|
||||||
|
request: ParseRecipeURLRequestV1
|
||||||
|
): String = networkRequestWrapper.makeCallAndHandleUnauthorized(
|
||||||
|
block = { service.createRecipeFromURL("$baseUrl/api/recipes/create-url", token, request) },
|
||||||
|
logMethod = { "parseRecipeFromURL" },
|
||||||
|
logParameters = { "baseUrl = $baseUrl, token = $token, request = $request" }
|
||||||
|
|
||||||
|
)
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -46,4 +46,11 @@ interface MealieServiceV1 {
|
|||||||
@Url url: String,
|
@Url url: String,
|
||||||
@Header(AUTHORIZATION_HEADER_NAME) token: String?,
|
@Header(AUTHORIZATION_HEADER_NAME) token: String?,
|
||||||
): GetRecipeResponseV1
|
): GetRecipeResponseV1
|
||||||
|
|
||||||
|
@POST
|
||||||
|
suspend fun createRecipeFromURL(
|
||||||
|
@Url url: String,
|
||||||
|
@Header(AUTHORIZATION_HEADER_NAME) token: String?,
|
||||||
|
@Body request: ParseRecipeURLRequestV1,
|
||||||
|
): String
|
||||||
}
|
}
|
||||||
@@ -0,0 +1,10 @@
|
|||||||
|
package gq.kirmanak.mealient.datasource.v1.models
|
||||||
|
|
||||||
|
import kotlinx.serialization.SerialName
|
||||||
|
import kotlinx.serialization.Serializable
|
||||||
|
|
||||||
|
@Serializable
|
||||||
|
data class ParseRecipeURLRequestV1(
|
||||||
|
@SerialName("url") val url: String,
|
||||||
|
@SerialName("includeTags") val includeTags: Boolean
|
||||||
|
)
|
||||||
@@ -75,6 +75,8 @@ chucker = "3.5.2"
|
|||||||
desugar = "1.2.2"
|
desugar = "1.2.2"
|
||||||
# https://github.com/google/ksp/releases
|
# https://github.com/google/ksp/releases
|
||||||
kspPlugin = "1.7.20-1.0.7"
|
kspPlugin = "1.7.20-1.0.7"
|
||||||
|
# https://developer.android.com/jetpack/androidx/releases/sharetarget
|
||||||
|
shareTarget = "1.2.0"
|
||||||
|
|
||||||
[libraries]
|
[libraries]
|
||||||
android-gradlePlugin = { group = "com.android.tools.build", name = "gradle", version.ref = "androidGradlePlugin" }
|
android-gradlePlugin = { group = "com.android.tools.build", name = "gradle", version.ref = "androidGradlePlugin" }
|
||||||
@@ -111,6 +113,7 @@ androidx-constraintLayout = { group = "androidx.constraintlayout", name = "const
|
|||||||
androidx-swipeRefreshLayout = { group = "androidx.swiperefreshlayout", name = "swiperefreshlayout", version.ref = "swipeRefreshLayout" }
|
androidx-swipeRefreshLayout = { group = "androidx.swiperefreshlayout", name = "swiperefreshlayout", version.ref = "swipeRefreshLayout" }
|
||||||
androidx-splashScreen = { group = "androidx.core", name = "core-splashscreen", version.ref = "splashScreen" }
|
androidx-splashScreen = { group = "androidx.core", name = "core-splashscreen", version.ref = "splashScreen" }
|
||||||
androidx-coreTesting = { group = "androidx.arch.core", name = "core-testing", version.ref = "coreTesting" }
|
androidx-coreTesting = { group = "androidx.arch.core", name = "core-testing", version.ref = "coreTesting" }
|
||||||
|
androidx-shareTarget = { group = "androidx.sharetarget", name = "sharetarget", version.ref = "shareTarget" }
|
||||||
|
|
||||||
androidx-paging-runtimeKtx = { group = "androidx.paging", name = "paging-runtime-ktx", version.ref = "paging" }
|
androidx-paging-runtimeKtx = { group = "androidx.paging", name = "paging-runtime-ktx", version.ref = "paging" }
|
||||||
androidx-paging-commonKtx = { group = "androidx.paging", name = "paging-common-ktx", version.ref = "paging" }
|
androidx-paging-commonKtx = { group = "androidx.paging", name = "paging-common-ktx", version.ref = "paging" }
|
||||||
|
|||||||
Reference in New Issue
Block a user