Skip to content

Navigation Menu

Sign in
Appearance settings

Search code, repositories, users, issues, pull requests...

Provide feedback

We read every piece of feedback, and take your input very seriously.

Saved searches

Use saved searches to filter your results more quickly

Appearance settings
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions 1 app/build.gradle.kts
Original file line number Diff line number Diff line change
Expand Up @@ -45,6 +45,7 @@ dependencies {
implementation(libs.androidx.lifecycle.runtime.ktx)
implementation(libs.androidx.navigation.fragment)
implementation(libs.androidx.navigation.ui)
implementation(libs.glide)
implementation(libs.material)
implementation(libs.androidx.activity)
implementation(libs.androidx.constraintlayout)
Expand Down
15 changes: 15 additions & 0 deletions 15 app/src/main/kotlin/ru/otus/cookbook/ui/CategoryViewHolder.kt
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
package ru.otus.cookbook.ui

import android.view.View
import android.widget.TextView
import androidx.recyclerview.widget.RecyclerView
import ru.otus.cookbook.R
import ru.otus.cookbook.data.RecipeListItem

class CategoryViewHolder(view: View) : RecyclerView.ViewHolder(view) {
private val categoryName = view.findViewById<TextView>(R.id.category_name_tv)

fun bind(item: RecipeListItem.CategoryItem) {
categoryName.text = item.name
}
}
66 changes: 66 additions & 0 deletions 66 app/src/main/kotlin/ru/otus/cookbook/ui/CookBookAdapter.kt
Original file line number Diff line number Diff line change
@@ -0,0 +1,66 @@
package ru.otus.cookbook.ui

import android.view.LayoutInflater
import android.view.ViewGroup
import androidx.recyclerview.widget.DiffUtil
import androidx.recyclerview.widget.ListAdapter
import androidx.recyclerview.widget.RecyclerView
import ru.otus.cookbook.R
import ru.otus.cookbook.data.RecipeListItem

class CookBookAdapter(private val itemClickListener: ItemClickListener) :
ListAdapter<RecipeListItem, RecyclerView.ViewHolder>(DiffCallback()) {
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): RecyclerView.ViewHolder {
return when (viewType) {
ViewTypes.RECIPE.ordinal -> {
val view = LayoutInflater.from(parent.context)
.inflate(R.layout.vh_recipe_item, parent, false)
RecipeViewHolder(view, itemClickListener)
}

ViewTypes.CATEGORY.ordinal -> {
val view = LayoutInflater.from(parent.context)
.inflate(R.layout.vh_recipe_category, parent, false)
CategoryViewHolder(view)
}

else -> throw IllegalArgumentException("View type is not supported")
}
}

override fun onBindViewHolder(holder: RecyclerView.ViewHolder, position: Int) {
when (holder) {
is RecipeViewHolder -> holder.bind(getItem(position) as RecipeListItem.RecipeItem)
is CategoryViewHolder -> holder.bind(getItem(position) as RecipeListItem.CategoryItem)
}
}

override fun getItemViewType(position: Int): Int {
return when (currentList[position]) {
is RecipeListItem.CategoryItem -> ViewTypes.CATEGORY.ordinal
is RecipeListItem.RecipeItem -> ViewTypes.RECIPE.ordinal
}
}
}

enum class ViewTypes {
CATEGORY,
RECIPE
}

class DiffCallback : DiffUtil.ItemCallback<RecipeListItem>() {
override fun areItemsTheSame(oldItem: RecipeListItem, newItem: RecipeListItem): Boolean {
return when {
oldItem is RecipeListItem.RecipeItem && newItem is RecipeListItem.RecipeItem -> oldItem.id == newItem.id
oldItem is RecipeListItem.CategoryItem && newItem is RecipeListItem.CategoryItem ->
oldItem.name == newItem.name

else -> false
}
}

override fun areContentsTheSame(oldItem: RecipeListItem, newItem: RecipeListItem): Boolean {
return oldItem == newItem
}

}
28 changes: 24 additions & 4 deletions 28 app/src/main/kotlin/ru/otus/cookbook/ui/CookbookFragment.kt
Original file line number Diff line number Diff line change
Expand Up @@ -8,19 +8,23 @@ import androidx.fragment.app.Fragment
import androidx.fragment.app.viewModels
import androidx.lifecycle.flowWithLifecycle
import androidx.lifecycle.lifecycleScope
import androidx.navigation.fragment.findNavController
import com.google.android.material.dialog.MaterialAlertDialogBuilder
import kotlinx.coroutines.launch
import ru.otus.cookbook.R
import ru.otus.cookbook.data.RecipeListItem
import ru.otus.cookbook.databinding.FragmentCookbookBinding

class CookbookFragment : Fragment() {
class CookbookFragment : Fragment(), ItemClickListener {

private val binding = FragmentBindingDelegate<FragmentCookbookBinding>(this)
private val model: CookbookFragmentViewModel by viewModels { CookbookFragmentViewModel.Factory }
private val adapter: CookBookAdapter by lazy { CookBookAdapter(this) }

override fun onCreateView(
inflater: LayoutInflater,
container: ViewGroup?,
savedInstanceState: Bundle?
savedInstanceState: Bundle?,
): View = binding.bind(
container,
FragmentCookbookBinding::inflate
Expand All @@ -34,13 +38,29 @@ class CookbookFragment : Fragment() {
.flowWithLifecycle(viewLifecycleOwner.lifecycle)
.collect(::onRecipeListUpdated)
}
binding.withBinding {
recipeListMt.setNavigationOnClickListener {
MaterialAlertDialogBuilder(requireContext())
.setMessage(getString(R.string.close_app_message))
.setPositiveButton(R.string.yes_btn) { _, _ ->
requireActivity().finishAndRemoveTask()
}
.setNegativeButton(R.string.no_btn, null)
.show()
}
}
}

private fun setupRecyclerView() = binding.withBinding {
// Setup RecyclerView
recipeListRv.adapter = adapter
}

private fun onRecipeListUpdated(recipeList: List<RecipeListItem>) {
// Handle recipe list
adapter.submitList(recipeList)
}

override fun itemClicked(id: Int) {
val action = CookbookFragmentDirections.actionCookbookFragmentToRecipeFragment(id)
findNavController().navigate(action)
}
}
41 changes: 41 additions & 0 deletions 41 app/src/main/kotlin/ru/otus/cookbook/ui/DeleteDialogFragment.kt
Original file line number Diff line number Diff line change
@@ -0,0 +1,41 @@
package ru.otus.cookbook.ui

import android.app.Dialog
import android.os.Bundle
import androidx.fragment.app.DialogFragment
import androidx.navigation.fragment.findNavController
import com.google.android.material.dialog.MaterialAlertDialogBuilder
import ru.otus.cookbook.R

class DeleteDialogFragment : DialogFragment() {
override fun onCreateDialog(savedInstanceState: Bundle?): Dialog {
return MaterialAlertDialogBuilder(requireContext())
.setTitle(getString(R.string.delete_dialog_title))
.setMessage(
getString(
R.string.delete_dialog_message,
DeleteDialogFragmentArgs.fromBundle(requireArguments()).recipeTitle
)
)
.setPositiveButton(R.string.ok_btn) { _, _ ->
dismiss()
setConfirmationResult(true)
}
.setNegativeButton(R.string.cancel_btn) { _, _ ->
dismiss()
setConfirmationResult(false)
}
.create()
}

private fun setConfirmationResult(result: Boolean) {
findNavController().previousBackStackEntry?.savedStateHandle?.set(
DELETE_CONFIRMATION_RESULT, result
)
}

companion object {
const val DELETE_CONFIRMATION_RESULT = "result"
}

}
5 changes: 5 additions & 0 deletions 5 app/src/main/kotlin/ru/otus/cookbook/ui/ItemClickListener.kt
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
package ru.otus.cookbook.ui

interface ItemClickListener {
fun itemClicked(id: Int)
}
52 changes: 49 additions & 3 deletions 52 app/src/main/kotlin/ru/otus/cookbook/ui/RecipeFragment.kt
Original file line number Diff line number Diff line change
Expand Up @@ -6,16 +6,21 @@ import android.view.View
import android.view.ViewGroup
import androidx.fragment.app.Fragment
import androidx.fragment.app.viewModels
import androidx.lifecycle.Lifecycle
import androidx.lifecycle.LifecycleEventObserver
import androidx.lifecycle.flowWithLifecycle
import androidx.lifecycle.lifecycleScope
import androidx.lifecycle.viewmodel.MutableCreationExtras
import androidx.navigation.fragment.findNavController
import com.bumptech.glide.Glide
import kotlinx.coroutines.launch
import ru.otus.cookbook.R
import ru.otus.cookbook.data.Recipe
import ru.otus.cookbook.databinding.FragmentRecipeBinding

class RecipeFragment : Fragment() {

private val recipeId: Int get() = TODO("Use Safe Args to get the recipe ID: https://developer.android.com/guide/navigation/use-graph/pass-data#Safe-args")
private val recipeId: Int get() = RecipeFragmentArgs.fromBundle(requireArguments()).recipeId

private val binding = FragmentBindingDelegate<FragmentRecipeBinding>(this)
private val model: RecipeFragmentViewModel by viewModels(
Expand All @@ -30,7 +35,7 @@ class RecipeFragment : Fragment() {
override fun onCreateView(
inflater: LayoutInflater,
container: ViewGroup?,
savedInstanceState: Bundle?
savedInstanceState: Bundle?,
): View = binding.bind(
container,
FragmentRecipeBinding::inflate
Expand All @@ -43,6 +48,38 @@ class RecipeFragment : Fragment() {
.flowWithLifecycle(viewLifecycleOwner.lifecycle)
.collect(::displayRecipe)
}
binding.withBinding {
detailRecipeMt.setNavigationOnClickListener {
findNavController().popBackStack()
}
detailRecipeMt.setOnMenuItemClickListener {
val action = RecipeFragmentDirections.actionRecipeFragmentToDeleteDialogFragment(getTitle())
findNavController().navigate(action)
true
}
}

val navBackStackEntry = findNavController().getBackStackEntry(R.id.recipeFragment)

val observer = LifecycleEventObserver { _, event ->
if (event == Lifecycle.Event.ON_RESUME &&
navBackStackEntry.savedStateHandle.contains(DeleteDialogFragment.DELETE_CONFIRMATION_RESULT)
) {
if (navBackStackEntry
.savedStateHandle.get<Boolean>(DeleteDialogFragment.DELETE_CONFIRMATION_RESULT) == true
) {
deleteRecipe()
findNavController().popBackStack()
}
}
}

navBackStackEntry.lifecycle.addObserver(observer)
viewLifecycleOwner.lifecycle.addObserver(LifecycleEventObserver { _, event ->
if (event == Lifecycle.Event.ON_DESTROY) {
navBackStackEntry.lifecycle.removeObserver(observer)
}
})
}

/**
Expand All @@ -53,7 +90,16 @@ class RecipeFragment : Fragment() {
}

private fun displayRecipe(recipe: Recipe) {
// Display the recipe
binding.withBinding {
detailRecipeMt.title = recipe.title
detailRecipeTitleTv.text = recipe.title
detailRecipeDescriptionTv.text = recipe.description
detailRecipeStepsTv.text = recipe.steps.joinToString(".")
Glide.with(this@RecipeFragment)
.load(recipe.imageUrl)
.centerCrop()
.into(detailRecipeImageIv)
}
}

private fun deleteRecipe() {
Expand Down
31 changes: 31 additions & 0 deletions 31 app/src/main/kotlin/ru/otus/cookbook/ui/RecipeViewHolder.kt
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
package ru.otus.cookbook.ui

import android.view.View
import android.widget.ImageView
import android.widget.TextView
import androidx.constraintlayout.widget.ConstraintLayout
import androidx.recyclerview.widget.RecyclerView
import com.bumptech.glide.Glide
import ru.otus.cookbook.R
import ru.otus.cookbook.data.RecipeListItem

class RecipeViewHolder(view: View, private val itemClickListener: ItemClickListener) : RecyclerView.ViewHolder(view) {
private val root = view.findViewById<ConstraintLayout>(R.id.recipe_item_cl)
private val letter = view.findViewById<TextView>(R.id.letter_tv)
private val title = view.findViewById<TextView>(R.id.title_tv)
private val description = view.findViewById<TextView>(R.id.description_tv)
private val image = view.findViewById<ImageView>(R.id.item_iv)

fun bind(item: RecipeListItem.RecipeItem) {

letter.text = item.title.first().toString()
title.text = item.title
description.text = item.description
Glide.with(root.context)
.load(item.imageUrl)
.centerCrop()
.circleCrop()
.into(image)
root.setOnClickListener { itemClickListener.itemClicked(item.id) }
}
}
16 changes: 16 additions & 0 deletions 16 app/src/main/res/drawable/bg_list_item_rounded_corners.xml
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
<shape
xmlns:android="http://schemas.android.com/apk/res/android">
<solid android:color="@android:color/transparent" />

<stroke
android:width="1dp"
android:color="?attr/colorOutlineVariant"
/>

<corners
android:bottomLeftRadius="@dimen/standard"
android:bottomRightRadius="@dimen/standard"
android:topLeftRadius="@dimen/standard"
android:topRightRadius="@dimen/standard"
/>
</shape>
11 changes: 11 additions & 0 deletions 11 app/src/main/res/drawable/circle_shape.xml
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
<?xml version="1.0" encoding="utf-8"?>
<shape
xmlns:android="http://schemas.android.com/apk/res/android"
android:shape="oval">
<solid android:color="?attr/colorPrimaryFixed" />

<size
android:height="40dp"
android:width="40dp" />

</shape>
13 changes: 13 additions & 0 deletions 13 app/src/main/res/drawable/ic_arrow.xml
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:width="40dp"
android:height="40dp"
android:viewportWidth="40"
android:viewportHeight="40">
<group>
<clip-path
android:pathData="M20,0L20,0A20,20 0,0 1,40 20L40,20A20,20 0,0 1,20 40L20,40A20,20 0,0 1,0 20L0,20A20,20 0,0 1,20 0z"/>
<path
android:pathData="M15.825,21L21.425,26.6L20,28L12,20L20,12L21.425,13.4L15.825,19H28V21H15.825Z"
android:fillColor="?attr/colorOnBackground"/>
</group>
</vector>
9 changes: 9 additions & 0 deletions 9 app/src/main/res/drawable/ic_close.xml
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:width="40dp"
android:height="40dp"
android:viewportWidth="40"
android:viewportHeight="40">
<path
android:pathData="M14.4,27L13,25.6L18.6,20L13,14.4L14.4,13L20,18.6L25.6,13L27,14.4L21.4,20L27,25.6L25.6,27L20,21.4L14.4,27Z"
android:fillColor="?attr/colorOnBackground"/>
</vector>
9 changes: 9 additions & 0 deletions 9 app/src/main/res/drawable/ic_trash.xml
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:width="24dp"
android:height="24dp"
android:viewportWidth="24"
android:viewportHeight="24">
<path
android:pathData="M7,21C6.45,21 5.979,20.804 5.588,20.413C5.196,20.021 5,19.55 5,19V6H4V4H9V3H15V4H20V6H19V19C19,19.55 18.804,20.021 18.413,20.413C18.021,20.804 17.55,21 17,21H7ZM17,6H7V19H17V6ZM9,17H11V8H9V17ZM13,17H15V8H13V17Z"
android:fillColor="?attr/colorOnBackground"/>
</vector>
Loading
Morty Proxy This is a proxified and sanitized view of the page, visit original site.