Skip to content
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
69 changes: 69 additions & 0 deletions app/src/main/kotlin/ru/otus/cookbook/ui/CookbookAdapter.kt
Original file line number Diff line number Diff line change
@@ -0,0 +1,69 @@
package ru.otus.cookbook.ui

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


class CookbookAdapter(
private val listener: Listener
) : ListAdapter<RecipeListItem, RecyclerView.ViewHolder>(DiffUtilItem()) {

override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): RecyclerView.ViewHolder {
return when (viewType) {
RecipeListItem.RecipeItem.layoutId -> {
val view = LayoutInflater.from(parent.context)
.inflate(R.layout.vh_recipe_item, parent, false)
RecipeViewHolder(view, listener)
}

RecipeListItem.CategoryItem.layoutId -> {
val view = LayoutInflater.from(parent.context)
.inflate(R.layout.vh_recipe_category, parent, false)
CategoryViewHolder(view)
}

else -> throw IllegalArgumentException("Not found view type for chat adapter")
}
}

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.RecipeItem -> RecipeListItem.RecipeItem.layoutId
is RecipeListItem.CategoryItem -> RecipeListItem.CategoryItem.layoutId
else -> -1
}
}
}

class DiffUtilItem : DiffUtil.ItemCallback<RecipeListItem>() {
override fun areItemsTheSame(oldItem: RecipeListItem, newItem: RecipeListItem): Boolean {
if (oldItem::class != newItem::class) return false

return when {
oldItem is RecipeListItem.CategoryItem && newItem is RecipeListItem.CategoryItem -> oldItem.name == newItem.name
oldItem is RecipeListItem.RecipeItem && newItem is RecipeListItem.RecipeItem -> oldItem.id == newItem.id
else -> false
}
}

override fun areContentsTheSame(oldItem: RecipeListItem, newItem: RecipeListItem): Boolean {
return when {
oldItem is RecipeListItem.CategoryItem && newItem is RecipeListItem.CategoryItem -> oldItem.name == newItem.name
oldItem is RecipeListItem.RecipeItem && newItem is RecipeListItem.RecipeItem -> oldItem.title == newItem.title && oldItem.description == newItem.description && oldItem.imageUrl == newItem.imageUrl
else -> false
}
}

}
17 changes: 14 additions & 3 deletions app/src/main/kotlin/ru/otus/cookbook/ui/CookbookFragment.kt
Original file line number Diff line number Diff line change
@@ -1,21 +1,25 @@
package ru.otus.cookbook.ui

import android.os.Bundle
import android.util.Log
import android.view.LayoutInflater
import android.view.View
import android.view.ViewGroup
import androidx.fragment.app.Fragment
import androidx.fragment.app.viewModels
import androidx.lifecycle.flowWithLifecycle
import androidx.lifecycle.lifecycleScope
import androidx.navigation.fragment.findNavController
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(), Listener {

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,
Expand All @@ -37,10 +41,17 @@ class CookbookFragment : Fragment() {
}

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

}

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

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

import android.os.Bundle
import androidx.fragment.app.Fragment
import android.view.LayoutInflater
import android.view.View
import android.view.ViewGroup
import android.widget.TextView
import androidx.fragment.app.DialogFragment
import androidx.navigation.fragment.findNavController
import ru.otus.cookbook.R


class DeleteDialogFragment : DialogFragment() {

override fun onCreateView(
inflater: LayoutInflater, container: ViewGroup?,
savedInstanceState: Bundle?
): View? {
return inflater.inflate(R.layout.fragment_delete_dialog, container, false)
}

override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
super.onViewCreated(view, savedInstanceState)
val dialogText = view.findViewById<TextView>(R.id.dialogText)

dialogText.text = getString(
R.string.are_you_sure_you_wand_to_delete,
DeleteDialogFragmentArgs.fromBundle(requireArguments()).title
)
view.findViewById<TextView>(R.id.ok).setOnClickListener {
val navController = findNavController()
navController.previousBackStackEntry?.savedStateHandle?.set(CONFIRMATION_RESULT, true)
navController.popBackStack()
}
view.findViewById<TextView>(R.id.cancel).setOnClickListener {
findNavController().popBackStack()
}
}

companion object {
const val CONFIRMATION_RESULT = "result"
}
}
5 changes: 5 additions & 0 deletions app/src/main/kotlin/ru/otus/cookbook/ui/Listener.kt
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
package ru.otus.cookbook.ui

interface Listener {
fun onItemClicked(id: Int)
}
50 changes: 47 additions & 3 deletions 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,22 @@ import android.view.View
import android.view.ViewGroup
import androidx.fragment.app.Fragment
import androidx.fragment.app.viewModels
import androidx.lifecycle.DefaultLifecycleObserver
import androidx.lifecycle.Lifecycle
import androidx.lifecycle.LifecycleEventObserver
import androidx.lifecycle.LifecycleOwner
import androidx.lifecycle.flowWithLifecycle
import androidx.lifecycle.lifecycleScope
import androidx.lifecycle.viewmodel.MutableCreationExtras
import androidx.navigation.fragment.findNavController
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 Down Expand Up @@ -43,6 +49,42 @@ class RecipeFragment : Fragment() {
.flowWithLifecycle(viewLifecycleOwner.lifecycle)
.collect(::displayRecipe)
}
binding.withBinding {
recipeMenuBar.setNavigationOnClickListener {
findNavController().popBackStack()
}
recipeMenuBar.setOnMenuItemClickListener { item ->
when (item.itemId) {
R.id.delete -> {
val action = RecipeFragmentDirections.actionRecipeFragmentToDeleteDialogFragment(getTitle())
findNavController().navigate(action)
true
}
else -> false
}
}
}

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

val observer = LifecycleEventObserver { _, event ->
if (event == Lifecycle.Event.ON_RESUME
&& navBackStackEntry.savedStateHandle.contains(DeleteDialogFragment.CONFIRMATION_RESULT)) {
val isConfirmed = navBackStackEntry.savedStateHandle.get<Boolean>(DeleteDialogFragment.CONFIRMATION_RESULT) ?: false;
if (isConfirmed) {
deleteRecipe()
findNavController().popBackStack()
}
}
}

navBackStackEntry.lifecycle.addObserver(observer)

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

/**
Expand All @@ -52,8 +94,10 @@ class RecipeFragment : Fragment() {
return model.recipe.value.title
}

private fun displayRecipe(recipe: Recipe) {
// Display the recipe
private fun displayRecipe(recipe: Recipe) = binding.withBinding {
title.text = recipe.title
desc.text = recipe.description
steps.text = recipe.steps.joinToString("\n")
}

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

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

class RecipeViewHolder (
private val view: View,
private val listener: Listener,
) : RecyclerView.ViewHolder(view) {
private val root: ConstraintLayout by lazy { view.findViewById(R.id.recipeItem) }
private val letter: TextView by lazy { view.findViewById(R.id.letter) }
private val title: TextView by lazy { view.findViewById(R.id.recipeTitle) }
private val desc: TextView by lazy { view.findViewById(R.id.recipeDesc) }

fun bind(item: RecipeListItem.RecipeItem) {
letter.text = item.title.substring(0, 1)
title.text = item.title
desc.text = item.description
root.setOnClickListener { listener.onItemClicked(item.id) }
}
}

class CategoryViewHolder (
private val view: View,
) : RecyclerView.ViewHolder(view) {
private val title: TextView by lazy { view.findViewById(R.id.categoryTitle) }

fun bind(item: RecipeListItem.CategoryItem) {
title.text = item.name
}
}
11 changes: 11 additions & 0 deletions app/src/main/res/drawable/arrow_back_24px.xml
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:width="24dp"
android:height="24dp"
android:viewportWidth="960"
android:viewportHeight="960"
android:tint="?attr/colorControlNormal"
android:autoMirrored="true">
<path
android:fillColor="@android:color/white"
android:pathData="M313,520L537,744L480,800L160,480L480,160L537,216L313,440L800,440L800,520L313,520Z"/>
</vector>
10 changes: 10 additions & 0 deletions app/src/main/res/drawable/delete_24px.xml
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:width="24dp"
android:height="24dp"
android:viewportWidth="960"
android:viewportHeight="960"
android:tint="?attr/colorControlNormal">
<path
android:fillColor="@android:color/white"
android:pathData="M280,840Q247,840 223.5,816.5Q200,793 200,760L200,240L160,240L160,160L360,160L360,120L600,120L600,160L800,160L800,240L760,240L760,760Q760,793 736.5,816.5Q713,840 680,840L280,840ZM680,240L280,240L280,760Q280,760 280,760Q280,760 280,760L680,760Q680,760 680,760Q680,760 680,760L680,240ZM360,680L440,680L440,320L360,320L360,680ZM520,680L600,680L600,320L520,320L520,680ZM280,240L280,240L280,760Q280,760 280,760Q280,760 280,760L280,760Q280,760 280,760Q280,760 280,760L280,240Z"/>
</vector>
21 changes: 10 additions & 11 deletions app/src/main/res/layout/activity_main.xml
Original file line number Diff line number Diff line change
@@ -1,19 +1,18 @@
<?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"
<FrameLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
android:id="@+id/main"
android:layout_width="match_parent"
android:layout_height="match_parent"
xmlns:app="http://schemas.android.com/apk/res-auto"
tools:context=".MainActivity">

<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="Hello World!"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent" />
<androidx.fragment.app.FragmentContainerView
android:id="@+id/cookbook"
android:name="androidx.navigation.fragment.NavHostFragment"
android:layout_width="match_parent"
android:layout_height="match_parent"
app:defaultNavHost="true"
app:navGraph="@navigation/navigation"/>

</androidx.constraintlayout.widget.ConstraintLayout>
</FrameLayout>
15 changes: 13 additions & 2 deletions app/src/main/res/layout/fragment_cookbook.xml
Original file line number Diff line number Diff line change
@@ -1,6 +1,17 @@
<?xml version="1.0" encoding="utf-8"?>
<androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools"
android:layout_height="match_parent">

</androidx.constraintlayout.widget.ConstraintLayout>
<androidx.recyclerview.widget.RecyclerView
android:id="@+id/cookbook"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="vertical"
app:layoutManager="androidx.recyclerview.widget.LinearLayoutManager"
tools:listitem="@layout/vh_recipe_item"
android:background="@color/white"
tools:itemCount="5" />
</LinearLayout>
Loading