Skip to content

Commit c6983e3

Browse files
committed
feature(recipe-detail):
1. add recipe detail page 2. add expandable text view library
1 parent 8a34ad6 commit c6983e3

File tree

35 files changed

+800
-18
lines changed

35 files changed

+800
-18
lines changed

.idea/gradle.xml

+2
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

app/src/main/res/navigation/app_navigation.xml

+18
Original file line numberDiff line numberDiff line change
@@ -29,6 +29,10 @@
2929
android:label="@string/title_recipe"
3030
tools:layout="@layout/fragment_recipe_list">
3131

32+
<action
33+
android:id="@+id/action_recipe_list_fragment_to_recipe_detail_fragment"
34+
app:destination="@id/navigation_recipe_detail" />
35+
3236
<argument
3337
android:name="@string/argument_food_category"
3438
android:defaultValue="@null"
@@ -37,4 +41,18 @@
3741

3842
</fragment>
3943

44+
<fragment
45+
android:id="@+id/navigation_recipe_detail"
46+
android:name="id.sansets.infood.recipe.presenter.detail.RecipeDetailFragment"
47+
android:label="@string/title_recipe_detail"
48+
tools:layout="@layout/fragment_recipe_detail">
49+
50+
<argument
51+
android:name="@string/argument_recipe"
52+
android:defaultValue="@null"
53+
app:argType="id.sansets.infood.core.domain.model.Recipe"
54+
app:nullable="true" />
55+
56+
</fragment>
57+
4058
</navigation>

app/src/main/res/values/strings.xml

+2
Original file line numberDiff line numberDiff line change
@@ -3,8 +3,10 @@
33

44
<string name="title_home">Home</string>
55
<string name="title_recipe">Recipe</string>
6+
<string name="title_recipe_detail">Recipe Detail</string>
67
<string name="title_favorite">Favorite</string>
78
<string name="title_filter">Filter</string>
89

910
<string name="argument_food_category">food_category</string>
11+
<string name="argument_recipe">recipe</string>
1012
</resources>

build.gradle

+1
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@ buildscript {
66
google()
77
jcenter()
88
}
9+
910
dependencies {
1011
classpath "com.android.tools.build:gradle:$gradleVersion"
1112
classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:$kotlinVersion"

core/build.gradle

+2-1
Original file line numberDiff line numberDiff line change
@@ -17,7 +17,8 @@ android {
1717
versionCode config.versionCode
1818
versionName config.versionName
1919

20-
buildConfigField("String", "BASE_URL", "\"https://api.spoonacular.com/\"")
20+
buildConfigField("String", "BASE_URL_API", "\"https://api.spoonacular.com/\"")
21+
buildConfigField("String", "BASE_URL", "\"https://spoonacular.com/\"")
2122
buildConfigField("String", "SPOONACULAR_API_KEY", properties.getProperty("spoonacular.key"))
2223

2324
testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner"
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,20 @@
1+
package id.sansets.infood.core.data.source.remote.response
2+
3+
import com.squareup.moshi.Json
4+
import com.squareup.moshi.JsonClass
5+
6+
@JsonClass(generateAdapter = true)
7+
data class IngredientResponse(
8+
9+
@Json(name = "id")
10+
val id: Int?,
11+
12+
@Json(name = "name")
13+
val name: String?,
14+
15+
@Json(name = "localizedName")
16+
val localizedName: String?,
17+
18+
@Json(name = "image")
19+
val image: String?,
20+
)
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,14 @@
1+
package id.sansets.infood.core.data.source.remote.response
2+
3+
import com.squareup.moshi.Json
4+
import com.squareup.moshi.JsonClass
5+
6+
@JsonClass(generateAdapter = true)
7+
data class InstructionResponse(
8+
9+
@Json(name = "name")
10+
val name: String?,
11+
12+
@Json(name = "steps")
13+
val steps: List<StepResponse>?
14+
)

core/src/main/java/id/sansets/infood/core/data/source/remote/response/RecipeResponse.kt

+13-1
Original file line numberDiff line numberDiff line change
@@ -12,9 +12,21 @@ data class RecipeResponse(
1212
@Json(name = "title")
1313
val title: String?,
1414

15+
@Json(name = "summary")
16+
val summary: String?,
17+
1518
@Json(name = "image")
1619
val imageUrl: String?,
1720

21+
@Json(name = "imageType")
22+
val imageType: String?,
23+
1824
@Json(name = "sourceName")
19-
val sourceName: String?
25+
val sourceName: String?,
26+
27+
@Json(name = "dishTypes")
28+
val dishTypes: List<String>?,
29+
30+
@Json(name = "analyzedInstructions")
31+
val analyzedInstructions: List<InstructionResponse>?
2032
)
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,17 @@
1+
package id.sansets.infood.core.data.source.remote.response
2+
3+
import com.squareup.moshi.Json
4+
import com.squareup.moshi.JsonClass
5+
6+
@JsonClass(generateAdapter = true)
7+
data class StepResponse(
8+
9+
@Json(name = "number")
10+
val number: Int?,
11+
12+
@Json(name = "step")
13+
val step: String?,
14+
15+
@Json(name = "ingredients")
16+
val ingredients: List<IngredientResponse>?
17+
)

core/src/main/java/id/sansets/infood/core/di/CoreNetworkModule.kt

+1-1
Original file line numberDiff line numberDiff line change
@@ -47,7 +47,7 @@ class CoreNetworkModule {
4747
@Provides
4848
fun provideCoreApiService(client: OkHttpClient): CoreApiService {
4949
val retrofit = Retrofit.Builder()
50-
.baseUrl(BuildConfig.BASE_URL)
50+
.baseUrl(BuildConfig.BASE_URL_API)
5151
.addConverterFactory(MoshiConverterFactory.create())
5252
.client(client)
5353
.build()
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,38 @@
11
package id.sansets.infood.core.domain.model
22

3+
import android.os.Parcelable
4+
import kotlinx.android.parcel.Parcelize
5+
6+
@Parcelize
37
data class Recipe(
48
val id: Int,
59
val title: String,
10+
val summary: String,
611
val imageUrl: String,
12+
val imageType: String,
713
val sourceName: String,
14+
val dishTypes: List<String>,
15+
val analyzedInstructions: List<Instruction>,
816
val isFavorite: Boolean
9-
)
17+
) : Parcelable
18+
19+
@Parcelize
20+
data class Instruction(
21+
val name: String,
22+
val steps: List<Step>
23+
) : Parcelable
24+
25+
@Parcelize
26+
data class Step(
27+
val number: Int,
28+
val step: String,
29+
val ingredients: List<Ingredient>
30+
) : Parcelable
31+
32+
@Parcelize
33+
data class Ingredient(
34+
val id: Int,
35+
val name: String,
36+
val localizedName: String,
37+
val image: String,
38+
) : Parcelable

core/src/main/java/id/sansets/infood/core/util/DataMapper.kt

+23-2
Original file line numberDiff line numberDiff line change
@@ -3,8 +3,7 @@ package id.sansets.infood.core.util
33
import id.sansets.infood.core.data.source.local.entity.FoodCategoryEntity
44
import id.sansets.infood.core.data.source.remote.response.FoodCategoryResponse
55
import id.sansets.infood.core.data.source.remote.response.RecipeResponse
6-
import id.sansets.infood.core.domain.model.FoodCategory
7-
import id.sansets.infood.core.domain.model.Recipe
6+
import id.sansets.infood.core.domain.model.*
87

98
object DataMapper {
109

@@ -33,8 +32,30 @@ object DataMapper {
3332
fun mapRecipeResponseToDomain(input: RecipeResponse): Recipe = Recipe(
3433
id = input.id,
3534
title = input.title ?: "",
35+
summary = input.summary ?: "",
3636
imageUrl = input.imageUrl ?: "",
37+
imageType = input.imageType ?: "",
3738
sourceName = input.sourceName ?: "",
39+
dishTypes = input.dishTypes ?: emptyList(),
40+
analyzedInstructions = input.analyzedInstructions?.map { instructionResponse ->
41+
Instruction(
42+
name = instructionResponse.name ?: "",
43+
steps = instructionResponse.steps?.map { stepResponse ->
44+
Step(
45+
number = stepResponse.number ?: 0,
46+
step = stepResponse.step ?: "",
47+
ingredients = stepResponse.ingredients?.map { ingredientResponse ->
48+
Ingredient(
49+
id = ingredientResponse.id ?: 0,
50+
name = ingredientResponse.name ?: "",
51+
localizedName = ingredientResponse.localizedName ?: "",
52+
image = ingredientResponse.image ?: ""
53+
)
54+
} ?: emptyList()
55+
)
56+
} ?: emptyList()
57+
)
58+
} ?: emptyList(),
3859
isFavorite = false,
3960
)
4061
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,14 @@
1+
package id.sansets.infood.core.util
2+
3+
import id.sansets.infood.core.BuildConfig
4+
5+
object UrlHelper {
6+
7+
fun getCoverUrl(id: String?, imageType: String): String {
8+
return "${BuildConfig.BASE_URL}/recipeImages/$id-312x231.$imageType"
9+
}
10+
11+
fun getBackdropUrl(id: String?, imageType: String): String {
12+
return "${BuildConfig.BASE_URL}/recipeImages/$id-636x393.$imageType"
13+
}
14+
}

core/src/main/java/id/sansets/infood/core/util/ViewExt.kt

+11
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,10 @@ package id.sansets.infood.core.util
22

33
import android.animation.Animator
44
import android.animation.AnimatorListenerAdapter
5+
import android.os.Build
6+
import android.text.Html
57
import android.view.View
8+
import android.widget.TextView
69
import androidx.core.view.ViewCompat
710
import androidx.core.widget.NestedScrollView
811
import androidx.recyclerview.widget.RecyclerView
@@ -77,4 +80,12 @@ fun RecyclerView.setAppBarElevationListener(appBar: AppBarLayout?) {
7780
}
7881
}
7982
})
83+
}
84+
85+
fun TextView.setTextFromHtml(text: String?) {
86+
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N) {
87+
setText(Html.fromHtml(text, Html.FROM_HTML_MODE_COMPACT))
88+
} else {
89+
setText(Html.fromHtml(text))
90+
}
8091
}

core/src/main/res/values/styles.xml

+11-1
Original file line numberDiff line numberDiff line change
@@ -48,6 +48,16 @@
4848
<item name="fontFamily">@font/raleway_semi_bold</item>
4949
</style>
5050

51+
<style name="TextView.Title.Section" parent="TextView.Title">
52+
<item name="android:layout_width">match_parent</item>
53+
<item name="android:layout_height">wrap_content</item>
54+
<item name="android:textSize">16sp</item>
55+
<item name="android:textColor">@android:color/primary_text_light</item>
56+
<item name="fontFamily">@font/raleway_semi_bold</item>
57+
<item name="textAllCaps">true</item>
58+
<item name="android:letterSpacing">0.075</item>
59+
</style>
60+
5161
<!-- End of TextView styles. -->
5262

5363

@@ -94,7 +104,7 @@
94104
<style name="Toolbar.Default" parent="Widget.MaterialComponents.Toolbar">
95105
<item name="android:layout_width">match_parent</item>
96106
<item name="android:layout_height">wrap_content</item>
97-
<item name="android:background">@android:color/white</item>
107+
<item name="android:backgroundTint">@android:color/white</item>
98108
<item name="fontFamily">@font/raleway_medium</item>
99109
<item name="titleTextColor">@android:color/black</item>
100110
<item name="titleTextAppearance">@style/TextView.Toolbar</item>

features/recipe/build.gradle

+1
Original file line numberDiff line numberDiff line change
@@ -38,6 +38,7 @@ dependencies {
3838
// Module
3939
implementation project(":app")
4040
implementation project(":core")
41+
implementation project(":libraries:expandable-textview")
4142

4243
// Support
4344
implementation supportDependencies.coreKtx

features/recipe/src/main/java/id/sansets/infood/recipe/di/RecipeViewModelModule.kt

+6
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@ import dagger.Module
77
import dagger.multibindings.IntoMap
88
import id.sansets.infood.core.di.ViewModelKey
99
import id.sansets.infood.core.util.ViewModelFactory
10+
import id.sansets.infood.recipe.presenter.detail.RecipeDetailViewModel
1011
import id.sansets.infood.recipe.presenter.filter.RecipeFilterViewModel
1112
import id.sansets.infood.recipe.presenter.list.RecipeListViewModel
1213
import kotlinx.coroutines.ExperimentalCoroutinesApi
@@ -30,4 +31,9 @@ abstract class RecipeViewModelModule {
3031
@IntoMap
3132
@ViewModelKey(RecipeFilterViewModel::class)
3233
abstract fun bindRecipeFilterViewModel(recipeFilterViewModel: RecipeFilterViewModel): ViewModel
34+
35+
@Binds
36+
@IntoMap
37+
@ViewModelKey(RecipeDetailViewModel::class)
38+
abstract fun bindRecipeDetailViewModel(recipeDetailViewModel: RecipeDetailViewModel): ViewModel
3339
}

0 commit comments

Comments
 (0)