Keep screen on and update docs (#179)
* Extract recipe screen components * Keep screen on while recipe is shown * Update screenshots * Add release note
@@ -30,7 +30,7 @@ information about each of the recipes. Moreover, you can create a recipe from th
|
|||||||
|
|
||||||
## Screenshots
|
## Screenshots
|
||||||
|
|
||||||
<img src="https://user-images.githubusercontent.com/24299495/203381442-0359cee1-e8a6-4d1f-bdff-eceb1dc31917.png" width="236" height="500" /> <img src="https://user-images.githubusercontent.com/24299495/203381431-51cb57aa-7a2b-4ada-8265-9d382bfae078.png" width="236" height="500" /> <img src="https://user-images.githubusercontent.com/24299495/203381424-358ec3b2-28d9-4237-985d-49be05ef3c7e.png" width="236" height="500" /> <img src="https://user-images.githubusercontent.com/24299495/202909845-d857259f-90f9-4988-beff-038cd784215d.png" width="236" height="500" />
|
<img src="https://github.com/kirmanak/Mealient/blob/master/fastlane/metadata/android/en-US/images/phoneScreenshots/1.png?raw=true" width="236" height="500" /> <img src="https://github.com/kirmanak/Mealient/blob/master/fastlane/metadata/android/en-US/images/phoneScreenshots/2.png?raw=true" width="236" height="500" /> <img src="https://github.com/kirmanak/Mealient/blob/master/fastlane/metadata/android/en-US/images/phoneScreenshots/3.png?raw=true" width="236" height="500" /> <img src="https://github.com/kirmanak/Mealient/blob/master/fastlane/metadata/android/en-US/images/phoneScreenshots/4.png?raw=true" width="236" height="500" /> <img src="https://github.com/kirmanak/Mealient/blob/master/fastlane/metadata/android/en-US/images/phoneScreenshots/5.png?raw=true" width="236" height="500" /> <img src="https://github.com/kirmanak/Mealient/blob/master/fastlane/metadata/android/en-US/images/phoneScreenshots/6.png?raw=true" width="236" height="500" />
|
||||||
|
|
||||||
## How to install
|
## How to install
|
||||||
|
|
||||||
|
|||||||
@@ -0,0 +1,14 @@
|
|||||||
|
package gq.kirmanak.mealient.extensions
|
||||||
|
|
||||||
|
import android.app.Activity
|
||||||
|
import android.content.Context
|
||||||
|
import android.content.ContextWrapper
|
||||||
|
|
||||||
|
fun Context.findActivity(): Activity? {
|
||||||
|
var context = this
|
||||||
|
while (context is ContextWrapper) {
|
||||||
|
if (context is Activity) return context
|
||||||
|
context = context.baseContext
|
||||||
|
}
|
||||||
|
return null
|
||||||
|
}
|
||||||
@@ -0,0 +1,88 @@
|
|||||||
|
package gq.kirmanak.mealient.ui.recipes.info
|
||||||
|
|
||||||
|
import androidx.compose.foundation.layout.Arrangement
|
||||||
|
import androidx.compose.foundation.layout.Column
|
||||||
|
import androidx.compose.foundation.layout.aspectRatio
|
||||||
|
import androidx.compose.foundation.layout.fillMaxWidth
|
||||||
|
import androidx.compose.foundation.layout.padding
|
||||||
|
import androidx.compose.foundation.shape.RoundedCornerShape
|
||||||
|
import androidx.compose.material3.MaterialTheme
|
||||||
|
import androidx.compose.material3.Text
|
||||||
|
import androidx.compose.runtime.Composable
|
||||||
|
import androidx.compose.ui.Alignment
|
||||||
|
import androidx.compose.ui.Modifier
|
||||||
|
import androidx.compose.ui.draw.clip
|
||||||
|
import androidx.compose.ui.layout.ContentScale
|
||||||
|
import androidx.compose.ui.res.painterResource
|
||||||
|
import androidx.compose.ui.res.stringResource
|
||||||
|
import androidx.compose.ui.tooling.preview.Preview
|
||||||
|
import androidx.compose.ui.unit.dp
|
||||||
|
import coil.compose.AsyncImage
|
||||||
|
import gq.kirmanak.mealient.R
|
||||||
|
import gq.kirmanak.mealient.ui.AppTheme
|
||||||
|
import gq.kirmanak.mealient.ui.Dimens
|
||||||
|
|
||||||
|
@Composable
|
||||||
|
internal fun HeaderSection(
|
||||||
|
imageUrl: String?,
|
||||||
|
title: String?,
|
||||||
|
description: String?,
|
||||||
|
) {
|
||||||
|
val imageFallback = painterResource(id = R.drawable.placeholder_recipe)
|
||||||
|
|
||||||
|
Column(
|
||||||
|
verticalArrangement = Arrangement.spacedBy(Dimens.Small, Alignment.Top),
|
||||||
|
) {
|
||||||
|
AsyncImage(
|
||||||
|
modifier = Modifier
|
||||||
|
.fillMaxWidth()
|
||||||
|
.aspectRatio(2f) // 2:1
|
||||||
|
.clip(
|
||||||
|
RoundedCornerShape(
|
||||||
|
topEnd = 0.dp,
|
||||||
|
topStart = 0.dp,
|
||||||
|
bottomEnd = Dimens.Intermediate,
|
||||||
|
bottomStart = Dimens.Intermediate,
|
||||||
|
)
|
||||||
|
),
|
||||||
|
model = imageUrl,
|
||||||
|
contentDescription = stringResource(id = R.string.content_description_fragment_recipe_info_image),
|
||||||
|
placeholder = imageFallback,
|
||||||
|
error = imageFallback,
|
||||||
|
fallback = imageFallback,
|
||||||
|
contentScale = ContentScale.Crop,
|
||||||
|
)
|
||||||
|
|
||||||
|
if (!title.isNullOrEmpty()) {
|
||||||
|
Text(
|
||||||
|
modifier = Modifier
|
||||||
|
.padding(horizontal = Dimens.Small),
|
||||||
|
text = title,
|
||||||
|
style = MaterialTheme.typography.headlineSmall,
|
||||||
|
color = MaterialTheme.colorScheme.onSurface,
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!description.isNullOrEmpty()) {
|
||||||
|
Text(
|
||||||
|
modifier = Modifier
|
||||||
|
.padding(horizontal = Dimens.Small),
|
||||||
|
text = description,
|
||||||
|
style = MaterialTheme.typography.bodyLarge,
|
||||||
|
color = MaterialTheme.colorScheme.onSurface,
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Preview
|
||||||
|
@Composable
|
||||||
|
private fun HeaderSectionPreview() {
|
||||||
|
AppTheme {
|
||||||
|
HeaderSection(
|
||||||
|
imageUrl = null,
|
||||||
|
title = "Recipe name",
|
||||||
|
description = "Recipe description",
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,111 @@
|
|||||||
|
package gq.kirmanak.mealient.ui.recipes.info
|
||||||
|
|
||||||
|
import androidx.compose.foundation.layout.Arrangement
|
||||||
|
import androidx.compose.foundation.layout.Column
|
||||||
|
import androidx.compose.foundation.layout.Row
|
||||||
|
import androidx.compose.foundation.layout.fillMaxWidth
|
||||||
|
import androidx.compose.foundation.layout.padding
|
||||||
|
import androidx.compose.material3.Card
|
||||||
|
import androidx.compose.material3.Checkbox
|
||||||
|
import androidx.compose.material3.Divider
|
||||||
|
import androidx.compose.material3.MaterialTheme
|
||||||
|
import androidx.compose.material3.Text
|
||||||
|
import androidx.compose.runtime.Composable
|
||||||
|
import androidx.compose.runtime.getValue
|
||||||
|
import androidx.compose.runtime.mutableStateOf
|
||||||
|
import androidx.compose.runtime.saveable.rememberSaveable
|
||||||
|
import androidx.compose.runtime.setValue
|
||||||
|
import androidx.compose.ui.Alignment
|
||||||
|
import androidx.compose.ui.Modifier
|
||||||
|
import androidx.compose.ui.res.stringResource
|
||||||
|
import androidx.compose.ui.tooling.preview.Preview
|
||||||
|
import gq.kirmanak.mealient.R
|
||||||
|
import gq.kirmanak.mealient.database.recipe.entity.RecipeIngredientEntity
|
||||||
|
import gq.kirmanak.mealient.ui.AppTheme
|
||||||
|
import gq.kirmanak.mealient.ui.Dimens
|
||||||
|
|
||||||
|
@Composable
|
||||||
|
internal fun IngredientsSection(
|
||||||
|
ingredients: List<RecipeIngredientEntity>,
|
||||||
|
) {
|
||||||
|
Column(
|
||||||
|
verticalArrangement = Arrangement.spacedBy(Dimens.Small, Alignment.Top),
|
||||||
|
) {
|
||||||
|
Text(
|
||||||
|
modifier = Modifier
|
||||||
|
.padding(horizontal = Dimens.Large),
|
||||||
|
text = stringResource(id = R.string.fragment_recipe_info_ingredients_header),
|
||||||
|
style = MaterialTheme.typography.titleLarge,
|
||||||
|
color = MaterialTheme.colorScheme.onSurface,
|
||||||
|
)
|
||||||
|
|
||||||
|
Card(
|
||||||
|
modifier = Modifier
|
||||||
|
.fillMaxWidth()
|
||||||
|
.padding(horizontal = Dimens.Small),
|
||||||
|
) {
|
||||||
|
Column(
|
||||||
|
modifier = Modifier
|
||||||
|
.fillMaxWidth()
|
||||||
|
.padding(Dimens.Small),
|
||||||
|
verticalArrangement = Arrangement.spacedBy(Dimens.Small, Alignment.Top),
|
||||||
|
) {
|
||||||
|
ingredients.forEach { item ->
|
||||||
|
IngredientListItem(
|
||||||
|
item = item,
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Composable
|
||||||
|
private fun IngredientListItem(
|
||||||
|
item: RecipeIngredientEntity,
|
||||||
|
modifier: Modifier = Modifier,
|
||||||
|
) {
|
||||||
|
var isChecked by rememberSaveable { mutableStateOf(false) }
|
||||||
|
|
||||||
|
val title = item.title
|
||||||
|
if (!title.isNullOrBlank()) {
|
||||||
|
Text(
|
||||||
|
modifier = modifier
|
||||||
|
.padding(horizontal = Dimens.Medium),
|
||||||
|
text = title,
|
||||||
|
style = MaterialTheme.typography.titleMedium,
|
||||||
|
color = MaterialTheme.colorScheme.onSurface,
|
||||||
|
)
|
||||||
|
|
||||||
|
Divider(
|
||||||
|
color = MaterialTheme.colorScheme.outline,
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
Row(
|
||||||
|
modifier = modifier,
|
||||||
|
horizontalArrangement = Arrangement.spacedBy(Dimens.Small, Alignment.Start),
|
||||||
|
verticalAlignment = Alignment.CenterVertically,
|
||||||
|
) {
|
||||||
|
Checkbox(
|
||||||
|
checked = isChecked,
|
||||||
|
onCheckedChange = { isChecked = it },
|
||||||
|
)
|
||||||
|
|
||||||
|
Text(
|
||||||
|
text = item.display,
|
||||||
|
style = MaterialTheme.typography.bodyMedium,
|
||||||
|
color = MaterialTheme.colorScheme.onSurface,
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Preview
|
||||||
|
@Composable
|
||||||
|
private fun IngredientsSectionPreview() {
|
||||||
|
AppTheme {
|
||||||
|
IngredientsSection(
|
||||||
|
ingredients = INGREDIENTS,
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,118 @@
|
|||||||
|
package gq.kirmanak.mealient.ui.recipes.info
|
||||||
|
|
||||||
|
import androidx.compose.foundation.layout.Arrangement
|
||||||
|
import androidx.compose.foundation.layout.Column
|
||||||
|
import androidx.compose.foundation.layout.fillMaxWidth
|
||||||
|
import androidx.compose.foundation.layout.padding
|
||||||
|
import androidx.compose.material3.Card
|
||||||
|
import androidx.compose.material3.Divider
|
||||||
|
import androidx.compose.material3.MaterialTheme
|
||||||
|
import androidx.compose.material3.Text
|
||||||
|
import androidx.compose.runtime.Composable
|
||||||
|
import androidx.compose.ui.Alignment
|
||||||
|
import androidx.compose.ui.Modifier
|
||||||
|
import androidx.compose.ui.res.stringResource
|
||||||
|
import androidx.compose.ui.tooling.preview.Preview
|
||||||
|
import gq.kirmanak.mealient.R
|
||||||
|
import gq.kirmanak.mealient.database.recipe.entity.RecipeIngredientEntity
|
||||||
|
import gq.kirmanak.mealient.database.recipe.entity.RecipeInstructionEntity
|
||||||
|
import gq.kirmanak.mealient.ui.AppTheme
|
||||||
|
import gq.kirmanak.mealient.ui.Dimens
|
||||||
|
|
||||||
|
@Composable
|
||||||
|
internal fun InstructionsSection(
|
||||||
|
instructions: Map<RecipeInstructionEntity, List<RecipeIngredientEntity>>,
|
||||||
|
) {
|
||||||
|
Column(
|
||||||
|
verticalArrangement = Arrangement.spacedBy(Dimens.Small, Alignment.Top),
|
||||||
|
) {
|
||||||
|
Text(
|
||||||
|
modifier = Modifier
|
||||||
|
.padding(horizontal = Dimens.Large),
|
||||||
|
text = stringResource(id = R.string.fragment_recipe_info_instructions_header),
|
||||||
|
style = MaterialTheme.typography.titleLarge,
|
||||||
|
color = MaterialTheme.colorScheme.onSurface,
|
||||||
|
)
|
||||||
|
|
||||||
|
var stepCount = 0
|
||||||
|
instructions.forEach { (instruction, ingredients) ->
|
||||||
|
InstructionListItem(
|
||||||
|
modifier = Modifier
|
||||||
|
.padding(horizontal = Dimens.Small),
|
||||||
|
item = instruction,
|
||||||
|
ingredients = ingredients,
|
||||||
|
index = stepCount++,
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Composable
|
||||||
|
private fun InstructionListItem(
|
||||||
|
item: RecipeInstructionEntity,
|
||||||
|
index: Int,
|
||||||
|
ingredients: List<RecipeIngredientEntity>,
|
||||||
|
modifier: Modifier = Modifier,
|
||||||
|
) {
|
||||||
|
val title = item.title
|
||||||
|
|
||||||
|
if (!title.isNullOrBlank()) {
|
||||||
|
Text(
|
||||||
|
modifier = modifier
|
||||||
|
.padding(horizontal = Dimens.Medium),
|
||||||
|
text = title,
|
||||||
|
style = MaterialTheme.typography.titleMedium,
|
||||||
|
color = MaterialTheme.colorScheme.onSurface,
|
||||||
|
)
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
Card(
|
||||||
|
modifier = modifier
|
||||||
|
.fillMaxWidth(),
|
||||||
|
) {
|
||||||
|
Column(
|
||||||
|
modifier = Modifier
|
||||||
|
.padding(Dimens.Medium),
|
||||||
|
verticalArrangement = Arrangement.spacedBy(Dimens.Small, Alignment.Top),
|
||||||
|
) {
|
||||||
|
Text(
|
||||||
|
text = stringResource(
|
||||||
|
R.string.view_holder_recipe_instructions_step,
|
||||||
|
index + 1
|
||||||
|
),
|
||||||
|
style = MaterialTheme.typography.titleSmall,
|
||||||
|
color = MaterialTheme.colorScheme.onSurface,
|
||||||
|
)
|
||||||
|
|
||||||
|
Text(
|
||||||
|
text = item.text.trim(),
|
||||||
|
style = MaterialTheme.typography.bodyMedium,
|
||||||
|
color = MaterialTheme.colorScheme.onSurface,
|
||||||
|
)
|
||||||
|
|
||||||
|
if (ingredients.isNotEmpty()) {
|
||||||
|
Divider(
|
||||||
|
color = MaterialTheme.colorScheme.outline,
|
||||||
|
)
|
||||||
|
ingredients.forEach { ingredient ->
|
||||||
|
Text(
|
||||||
|
text = ingredient.display,
|
||||||
|
style = MaterialTheme.typography.bodySmall,
|
||||||
|
color = MaterialTheme.colorScheme.onSurface,
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Preview
|
||||||
|
@Composable
|
||||||
|
private fun InstructionsSectionPreview() {
|
||||||
|
AppTheme {
|
||||||
|
InstructionsSection(
|
||||||
|
instructions = INSTRUCTIONS,
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,19 @@
|
|||||||
|
package gq.kirmanak.mealient.ui.recipes.info
|
||||||
|
|
||||||
|
import android.view.WindowManager
|
||||||
|
import androidx.compose.runtime.Composable
|
||||||
|
import androidx.compose.runtime.DisposableEffect
|
||||||
|
import androidx.compose.ui.platform.LocalContext
|
||||||
|
import gq.kirmanak.mealient.extensions.findActivity
|
||||||
|
|
||||||
|
@Composable
|
||||||
|
fun KeepScreenOn() {
|
||||||
|
val context = LocalContext.current
|
||||||
|
val window = context.findActivity()?.window ?: return
|
||||||
|
DisposableEffect(Unit) {
|
||||||
|
window.addFlags(WindowManager.LayoutParams.FLAG_KEEP_SCREEN_ON)
|
||||||
|
onDispose {
|
||||||
|
window.clearFlags(WindowManager.LayoutParams.FLAG_KEEP_SCREEN_ON)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,64 @@
|
|||||||
|
package gq.kirmanak.mealient.ui.recipes.info
|
||||||
|
|
||||||
|
import gq.kirmanak.mealient.database.recipe.entity.RecipeIngredientEntity
|
||||||
|
import gq.kirmanak.mealient.database.recipe.entity.RecipeInstructionEntity
|
||||||
|
import gq.kirmanak.mealient.database.recipe.entity.RecipeSummaryEntity
|
||||||
|
import kotlinx.datetime.LocalDate
|
||||||
|
import kotlinx.datetime.LocalDateTime
|
||||||
|
|
||||||
|
internal val INGREDIENT_TWO = RecipeIngredientEntity(
|
||||||
|
id = "2",
|
||||||
|
recipeId = "1",
|
||||||
|
note = "Recipe ingredient note",
|
||||||
|
food = "Recipe ingredient food",
|
||||||
|
unit = "Recipe ingredient unit",
|
||||||
|
display = "Recipe ingredient display that is very long and should be wrapped",
|
||||||
|
quantity = 1.0,
|
||||||
|
title = null,
|
||||||
|
)
|
||||||
|
|
||||||
|
internal val SUMMARY_ENTITY = RecipeSummaryEntity(
|
||||||
|
remoteId = "1",
|
||||||
|
name = "Recipe name",
|
||||||
|
slug = "recipe-name",
|
||||||
|
description = "Recipe description",
|
||||||
|
dateAdded = LocalDate(2021, 1, 1),
|
||||||
|
dateUpdated = LocalDateTime(2021, 1, 1, 1, 1, 1),
|
||||||
|
imageId = null,
|
||||||
|
isFavorite = false,
|
||||||
|
)
|
||||||
|
|
||||||
|
internal val INGREDIENT_ONE = RecipeIngredientEntity(
|
||||||
|
id = "1",
|
||||||
|
recipeId = "1",
|
||||||
|
note = "Recipe ingredient note",
|
||||||
|
food = "Recipe ingredient food",
|
||||||
|
unit = "Recipe ingredient unit",
|
||||||
|
display = "Recipe ingredient display that is very long and should be wrapped",
|
||||||
|
quantity = 1.0,
|
||||||
|
title = "Recipe ingredient section title",
|
||||||
|
)
|
||||||
|
|
||||||
|
internal val INSTRUCTION_ONE = RecipeInstructionEntity(
|
||||||
|
id = "1",
|
||||||
|
recipeId = "1",
|
||||||
|
text = "Recipe instruction",
|
||||||
|
title = "Section title",
|
||||||
|
)
|
||||||
|
|
||||||
|
internal val INSTRUCTION_TWO = RecipeInstructionEntity(
|
||||||
|
id = "2",
|
||||||
|
recipeId = "1",
|
||||||
|
text = "Recipe instruction",
|
||||||
|
title = "",
|
||||||
|
)
|
||||||
|
|
||||||
|
internal val INGREDIENTS = listOf(
|
||||||
|
INGREDIENT_ONE,
|
||||||
|
INGREDIENT_TWO,
|
||||||
|
)
|
||||||
|
|
||||||
|
internal val INSTRUCTIONS = mapOf(
|
||||||
|
INSTRUCTION_ONE to emptyList(),
|
||||||
|
INSTRUCTION_TWO to listOf(INGREDIENT_TWO),
|
||||||
|
)
|
||||||
@@ -4,45 +4,21 @@ import android.content.res.Configuration.UI_MODE_NIGHT_MASK
|
|||||||
import android.content.res.Configuration.UI_MODE_NIGHT_YES
|
import android.content.res.Configuration.UI_MODE_NIGHT_YES
|
||||||
import androidx.compose.foundation.layout.Arrangement
|
import androidx.compose.foundation.layout.Arrangement
|
||||||
import androidx.compose.foundation.layout.Column
|
import androidx.compose.foundation.layout.Column
|
||||||
import androidx.compose.foundation.layout.Row
|
|
||||||
import androidx.compose.foundation.layout.aspectRatio
|
|
||||||
import androidx.compose.foundation.layout.fillMaxWidth
|
|
||||||
import androidx.compose.foundation.layout.padding
|
|
||||||
import androidx.compose.foundation.rememberScrollState
|
import androidx.compose.foundation.rememberScrollState
|
||||||
import androidx.compose.foundation.shape.RoundedCornerShape
|
|
||||||
import androidx.compose.foundation.verticalScroll
|
import androidx.compose.foundation.verticalScroll
|
||||||
import androidx.compose.material3.Card
|
|
||||||
import androidx.compose.material3.Checkbox
|
|
||||||
import androidx.compose.material3.Divider
|
|
||||||
import androidx.compose.material3.MaterialTheme
|
|
||||||
import androidx.compose.material3.Text
|
|
||||||
import androidx.compose.runtime.Composable
|
import androidx.compose.runtime.Composable
|
||||||
import androidx.compose.runtime.getValue
|
|
||||||
import androidx.compose.runtime.mutableStateOf
|
|
||||||
import androidx.compose.runtime.saveable.rememberSaveable
|
|
||||||
import androidx.compose.runtime.setValue
|
|
||||||
import androidx.compose.ui.Alignment
|
import androidx.compose.ui.Alignment
|
||||||
import androidx.compose.ui.Modifier
|
import androidx.compose.ui.Modifier
|
||||||
import androidx.compose.ui.draw.clip
|
|
||||||
import androidx.compose.ui.layout.ContentScale
|
|
||||||
import androidx.compose.ui.res.painterResource
|
|
||||||
import androidx.compose.ui.res.stringResource
|
|
||||||
import androidx.compose.ui.tooling.preview.Preview
|
import androidx.compose.ui.tooling.preview.Preview
|
||||||
import androidx.compose.ui.unit.dp
|
|
||||||
import coil.compose.AsyncImage
|
|
||||||
import gq.kirmanak.mealient.R
|
|
||||||
import gq.kirmanak.mealient.database.recipe.entity.RecipeIngredientEntity
|
|
||||||
import gq.kirmanak.mealient.database.recipe.entity.RecipeInstructionEntity
|
|
||||||
import gq.kirmanak.mealient.database.recipe.entity.RecipeSummaryEntity
|
|
||||||
import gq.kirmanak.mealient.ui.AppTheme
|
import gq.kirmanak.mealient.ui.AppTheme
|
||||||
import gq.kirmanak.mealient.ui.Dimens
|
import gq.kirmanak.mealient.ui.Dimens
|
||||||
import kotlinx.datetime.LocalDate
|
|
||||||
import kotlinx.datetime.LocalDateTime
|
|
||||||
|
|
||||||
@Composable
|
@Composable
|
||||||
fun RecipeScreen(
|
fun RecipeScreen(
|
||||||
uiState: RecipeInfoUiState,
|
uiState: RecipeInfoUiState,
|
||||||
) {
|
) {
|
||||||
|
KeepScreenOn()
|
||||||
|
|
||||||
Column(
|
Column(
|
||||||
modifier = Modifier
|
modifier = Modifier
|
||||||
.verticalScroll(
|
.verticalScroll(
|
||||||
@@ -70,217 +46,12 @@ fun RecipeScreen(
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@Composable
|
|
||||||
private fun HeaderSection(
|
|
||||||
imageUrl: String?,
|
|
||||||
title: String?,
|
|
||||||
description: String?,
|
|
||||||
) {
|
|
||||||
val imageFallback = painterResource(id = R.drawable.placeholder_recipe)
|
|
||||||
|
|
||||||
AsyncImage(
|
|
||||||
modifier = Modifier
|
|
||||||
.fillMaxWidth()
|
|
||||||
.aspectRatio(2f) // 2:1
|
|
||||||
.clip(
|
|
||||||
RoundedCornerShape(
|
|
||||||
topEnd = 0.dp,
|
|
||||||
topStart = 0.dp,
|
|
||||||
bottomEnd = Dimens.Intermediate,
|
|
||||||
bottomStart = Dimens.Intermediate,
|
|
||||||
)
|
|
||||||
),
|
|
||||||
model = imageUrl,
|
|
||||||
contentDescription = stringResource(id = R.string.content_description_fragment_recipe_info_image),
|
|
||||||
placeholder = imageFallback,
|
|
||||||
error = imageFallback,
|
|
||||||
fallback = imageFallback,
|
|
||||||
contentScale = ContentScale.Crop,
|
|
||||||
)
|
|
||||||
|
|
||||||
if (!title.isNullOrEmpty()) {
|
|
||||||
Text(
|
|
||||||
modifier = Modifier
|
|
||||||
.padding(horizontal = Dimens.Small),
|
|
||||||
text = title,
|
|
||||||
style = MaterialTheme.typography.headlineSmall,
|
|
||||||
color = MaterialTheme.colorScheme.onSurface,
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!description.isNullOrEmpty()) {
|
|
||||||
Text(
|
|
||||||
modifier = Modifier
|
|
||||||
.padding(horizontal = Dimens.Small),
|
|
||||||
text = description,
|
|
||||||
style = MaterialTheme.typography.bodyLarge,
|
|
||||||
color = MaterialTheme.colorScheme.onSurface,
|
|
||||||
)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@Composable
|
|
||||||
private fun InstructionsSection(
|
|
||||||
instructions: Map<RecipeInstructionEntity, List<RecipeIngredientEntity>>,
|
|
||||||
) {
|
|
||||||
Text(
|
|
||||||
modifier = Modifier
|
|
||||||
.padding(horizontal = Dimens.Large),
|
|
||||||
text = stringResource(id = R.string.fragment_recipe_info_instructions_header),
|
|
||||||
style = MaterialTheme.typography.titleLarge,
|
|
||||||
color = MaterialTheme.colorScheme.onSurface,
|
|
||||||
)
|
|
||||||
|
|
||||||
var stepCount = 0
|
|
||||||
instructions.forEach { (instruction, ingredients) ->
|
|
||||||
InstructionListItem(
|
|
||||||
modifier = Modifier
|
|
||||||
.padding(horizontal = Dimens.Small),
|
|
||||||
item = instruction,
|
|
||||||
ingredients = ingredients,
|
|
||||||
index = stepCount++,
|
|
||||||
)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@Composable
|
|
||||||
private fun IngredientsSection(
|
|
||||||
ingredients: List<RecipeIngredientEntity>,
|
|
||||||
) {
|
|
||||||
Text(
|
|
||||||
modifier = Modifier
|
|
||||||
.padding(horizontal = Dimens.Large),
|
|
||||||
text = stringResource(id = R.string.fragment_recipe_info_ingredients_header),
|
|
||||||
style = MaterialTheme.typography.titleLarge,
|
|
||||||
color = MaterialTheme.colorScheme.onSurface,
|
|
||||||
)
|
|
||||||
|
|
||||||
Card(
|
|
||||||
modifier = Modifier
|
|
||||||
.fillMaxWidth()
|
|
||||||
.padding(horizontal = Dimens.Small),
|
|
||||||
) {
|
|
||||||
Column(
|
|
||||||
modifier = Modifier
|
|
||||||
.fillMaxWidth()
|
|
||||||
.padding(Dimens.Small),
|
|
||||||
verticalArrangement = Arrangement.spacedBy(Dimens.Small, Alignment.Top),
|
|
||||||
) {
|
|
||||||
ingredients.forEach { item ->
|
|
||||||
IngredientListItem(
|
|
||||||
item = item,
|
|
||||||
)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@Composable
|
|
||||||
private fun InstructionListItem(
|
|
||||||
item: RecipeInstructionEntity,
|
|
||||||
index: Int,
|
|
||||||
ingredients: List<RecipeIngredientEntity>,
|
|
||||||
modifier: Modifier = Modifier,
|
|
||||||
) {
|
|
||||||
val title = item.title
|
|
||||||
|
|
||||||
if (!title.isNullOrBlank()) {
|
|
||||||
Text(
|
|
||||||
modifier = modifier
|
|
||||||
.padding(horizontal = Dimens.Medium),
|
|
||||||
text = title,
|
|
||||||
style = MaterialTheme.typography.titleMedium,
|
|
||||||
color = MaterialTheme.colorScheme.onSurface,
|
|
||||||
)
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
Card(
|
|
||||||
modifier = modifier
|
|
||||||
.fillMaxWidth(),
|
|
||||||
) {
|
|
||||||
Column(
|
|
||||||
modifier = Modifier
|
|
||||||
.padding(Dimens.Medium),
|
|
||||||
verticalArrangement = Arrangement.spacedBy(Dimens.Small, Alignment.Top),
|
|
||||||
) {
|
|
||||||
Text(
|
|
||||||
text = stringResource(
|
|
||||||
R.string.view_holder_recipe_instructions_step,
|
|
||||||
index + 1
|
|
||||||
),
|
|
||||||
style = MaterialTheme.typography.titleSmall,
|
|
||||||
color = MaterialTheme.colorScheme.onSurface,
|
|
||||||
)
|
|
||||||
|
|
||||||
Text(
|
|
||||||
text = item.text.trim(),
|
|
||||||
style = MaterialTheme.typography.bodyMedium,
|
|
||||||
color = MaterialTheme.colorScheme.onSurface,
|
|
||||||
)
|
|
||||||
|
|
||||||
if (ingredients.isNotEmpty()) {
|
|
||||||
Divider(
|
|
||||||
color = MaterialTheme.colorScheme.outline,
|
|
||||||
)
|
|
||||||
ingredients.forEach { ingredient ->
|
|
||||||
Text(
|
|
||||||
text = ingredient.display,
|
|
||||||
style = MaterialTheme.typography.bodySmall,
|
|
||||||
color = MaterialTheme.colorScheme.onSurface,
|
|
||||||
)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@Composable
|
|
||||||
private fun IngredientListItem(
|
|
||||||
item: RecipeIngredientEntity,
|
|
||||||
modifier: Modifier = Modifier,
|
|
||||||
) {
|
|
||||||
var isChecked by rememberSaveable { mutableStateOf(false) }
|
|
||||||
|
|
||||||
val title = item.title
|
|
||||||
if (!title.isNullOrBlank()) {
|
|
||||||
Text(
|
|
||||||
modifier = modifier
|
|
||||||
.padding(horizontal = Dimens.Medium),
|
|
||||||
text = title,
|
|
||||||
style = MaterialTheme.typography.titleMedium,
|
|
||||||
color = MaterialTheme.colorScheme.onSurface,
|
|
||||||
)
|
|
||||||
|
|
||||||
Divider(
|
|
||||||
color = MaterialTheme.colorScheme.outline,
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
Row(
|
|
||||||
modifier = modifier,
|
|
||||||
horizontalArrangement = Arrangement.spacedBy(Dimens.Small, Alignment.Start),
|
|
||||||
verticalAlignment = Alignment.CenterVertically,
|
|
||||||
) {
|
|
||||||
Checkbox(
|
|
||||||
checked = isChecked,
|
|
||||||
onCheckedChange = { isChecked = it },
|
|
||||||
)
|
|
||||||
|
|
||||||
Text(
|
|
||||||
text = item.display,
|
|
||||||
style = MaterialTheme.typography.bodyMedium,
|
|
||||||
color = MaterialTheme.colorScheme.onSurface,
|
|
||||||
)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@Preview(showBackground = true)
|
@Preview(showBackground = true)
|
||||||
@Composable
|
@Composable
|
||||||
private fun RecipeScreenPreview() {
|
private fun RecipeScreenPreview() {
|
||||||
AppTheme {
|
AppTheme {
|
||||||
RecipeScreen(
|
RecipeScreen(
|
||||||
uiState = previewUiState()
|
uiState = RECIPE_INFO_UI_STATE
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -290,65 +61,17 @@ private fun RecipeScreenPreview() {
|
|||||||
private fun RecipeScreenNightPreview() {
|
private fun RecipeScreenNightPreview() {
|
||||||
AppTheme {
|
AppTheme {
|
||||||
RecipeScreen(
|
RecipeScreen(
|
||||||
uiState = previewUiState()
|
uiState = RECIPE_INFO_UI_STATE
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun previewUiState(): RecipeInfoUiState {
|
private val RECIPE_INFO_UI_STATE = RecipeInfoUiState(
|
||||||
val ingredient = RecipeIngredientEntity(
|
showIngredients = true,
|
||||||
id = "2",
|
showInstructions = true,
|
||||||
recipeId = "1",
|
summaryEntity = SUMMARY_ENTITY,
|
||||||
note = "Recipe ingredient note",
|
recipeIngredients = INGREDIENTS,
|
||||||
food = "Recipe ingredient food",
|
recipeInstructions = INSTRUCTIONS,
|
||||||
unit = "Recipe ingredient unit",
|
title = "Recipe title",
|
||||||
display = "Recipe ingredient display that is very long and should be wrapped",
|
description = "Recipe description",
|
||||||
quantity = 1.0,
|
)
|
||||||
title = null,
|
|
||||||
)
|
|
||||||
val uiState = RecipeInfoUiState(
|
|
||||||
showIngredients = true,
|
|
||||||
showInstructions = true,
|
|
||||||
summaryEntity = RecipeSummaryEntity(
|
|
||||||
remoteId = "1",
|
|
||||||
name = "Recipe name",
|
|
||||||
slug = "recipe-name",
|
|
||||||
description = "Recipe description",
|
|
||||||
dateAdded = LocalDate(2021, 1, 1),
|
|
||||||
dateUpdated = LocalDateTime(2021, 1, 1, 1, 1, 1),
|
|
||||||
imageId = null,
|
|
||||||
isFavorite = false,
|
|
||||||
),
|
|
||||||
recipeIngredients = listOf(
|
|
||||||
RecipeIngredientEntity(
|
|
||||||
id = "1",
|
|
||||||
recipeId = "1",
|
|
||||||
note = "Recipe ingredient note",
|
|
||||||
food = "Recipe ingredient food",
|
|
||||||
unit = "Recipe ingredient unit",
|
|
||||||
display = "Recipe ingredient display that is very long and should be wrapped",
|
|
||||||
quantity = 1.0,
|
|
||||||
title = "Recipe ingredient section title",
|
|
||||||
),
|
|
||||||
ingredient,
|
|
||||||
),
|
|
||||||
recipeInstructions = mapOf(
|
|
||||||
RecipeInstructionEntity(
|
|
||||||
id = "1",
|
|
||||||
recipeId = "1",
|
|
||||||
text = "Recipe instruction",
|
|
||||||
title = "Section title",
|
|
||||||
) to emptyList(),
|
|
||||||
RecipeInstructionEntity(
|
|
||||||
id = "2",
|
|
||||||
recipeId = "1",
|
|
||||||
text = "Recipe instruction",
|
|
||||||
title = "",
|
|
||||||
) to listOf(ingredient),
|
|
||||||
),
|
|
||||||
title = "Recipe title",
|
|
||||||
description = "Recipe description",
|
|
||||||
)
|
|
||||||
return uiState
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|||||||
1
fastlane/metadata/android/en-US/changelogs/31.txt
Normal file
@@ -0,0 +1 @@
|
|||||||
|
Ingredients that are linked to a specific recipe step are shown under that step.
|
||||||
|
Before Width: | Height: | Size: 3.7 MiB After Width: | Height: | Size: 2.0 MiB |
|
Before Width: | Height: | Size: 4.0 MiB After Width: | Height: | Size: 1.9 MiB |
|
Before Width: | Height: | Size: 741 KiB After Width: | Height: | Size: 171 KiB |
|
Before Width: | Height: | Size: 125 KiB After Width: | Height: | Size: 102 KiB |
BIN
fastlane/metadata/android/en-US/images/phoneScreenshots/5.png
Normal file
|
After Width: | Height: | Size: 38 KiB |
BIN
fastlane/metadata/android/en-US/images/phoneScreenshots/6.png
Normal file
|
After Width: | Height: | Size: 64 KiB |