diff --git a/android/app/build.gradle.kts b/android/app/build.gradle.kts index 8e58ecc1..e3fcb109 100644 --- a/android/app/build.gradle.kts +++ b/android/app/build.gradle.kts @@ -7,6 +7,7 @@ plugins { alias(libs.plugins.google.gms.google.services) id("kotlin-kapt") id("kotlin-parcelize") + id("androidx.navigation.safeargs.kotlin") } android { diff --git a/android/app/src/main/java/net/pengcook/android/presentation/MainActivity.kt b/android/app/src/main/java/net/pengcook/android/presentation/MainActivity.kt index 8ffa3bf9..180573c3 100644 --- a/android/app/src/main/java/net/pengcook/android/presentation/MainActivity.kt +++ b/android/app/src/main/java/net/pengcook/android/presentation/MainActivity.kt @@ -21,7 +21,7 @@ class MainActivity : AppCompatActivity() { navController.addOnDestinationChangedListener { _, destination, _ -> when (destination.id) { - R.id.homeFragment -> bottomNav.visibility = View.VISIBLE + R.id.homeFragment, R.id.searchFragment, R.id.profileFragment, R.id.categoryFragment -> bottomNav.visibility = View.VISIBLE else -> bottomNav.visibility = View.GONE } } diff --git a/android/app/src/main/java/net/pengcook/android/presentation/detail/DetailRecipeFragment.kt b/android/app/src/main/java/net/pengcook/android/presentation/detail/DetailRecipeFragment.kt index 8e7ec01f..d7fb67ed 100644 --- a/android/app/src/main/java/net/pengcook/android/presentation/detail/DetailRecipeFragment.kt +++ b/android/app/src/main/java/net/pengcook/android/presentation/detail/DetailRecipeFragment.kt @@ -5,13 +5,16 @@ import android.view.LayoutInflater import android.view.View import android.view.ViewGroup import androidx.fragment.app.Fragment +import androidx.navigation.fragment.findNavController +import androidx.navigation.fragment.navArgs import net.pengcook.android.databinding.FragmentDetailRecipeBinding import net.pengcook.android.presentation.core.model.Recipe class DetailRecipeFragment : Fragment() { + private val args: DetailRecipeFragmentArgs by navArgs() private val binding by lazy { FragmentDetailRecipeBinding.inflate(layoutInflater) } private val viewModel by lazy { DetailRecipeViewModel(recipe) } - private lateinit var recipe: Recipe + private val recipe: Recipe by lazy { args.recipe } override fun onCreateView( inflater: LayoutInflater, @@ -25,14 +28,26 @@ class DetailRecipeFragment : Fragment() { ) { super.onViewCreated(view, savedInstanceState) fetchRecipe() + observeNavigationEvent() } - private fun fetchRecipe() { - val argument = arguments?.getParcelable(RECIPE_KEY) as Recipe? - if (argument is Recipe) { - recipe = argument + private fun observeNavigationEvent() { + viewModel.navigateToStepEvent.observe(viewLifecycleOwner) { navigationEvent -> + val navigationAvailable = navigationEvent.getContentIfNotHandled() ?: return@observe + if (navigationAvailable) { + navigateToStep() + } } - binding.recipe = recipe + } + + private fun fetchRecipe() { + binding.recipe = args.recipe + binding.vm = viewModel + } + + private fun navigateToStep() { + val action = DetailRecipeFragmentDirections.actionDetailRecipeFragmentToRecipeStepFragment() + findNavController().navigate(action) } companion object { diff --git a/android/app/src/main/java/net/pengcook/android/presentation/detail/DetailRecipeViewModel.kt b/android/app/src/main/java/net/pengcook/android/presentation/detail/DetailRecipeViewModel.kt index 25c632c2..6017b61a 100644 --- a/android/app/src/main/java/net/pengcook/android/presentation/detail/DetailRecipeViewModel.kt +++ b/android/app/src/main/java/net/pengcook/android/presentation/detail/DetailRecipeViewModel.kt @@ -1,8 +1,19 @@ package net.pengcook.android.presentation.detail +// DetailRecipeViewModel.kt +import androidx.lifecycle.LiveData +import androidx.lifecycle.MutableLiveData import androidx.lifecycle.ViewModel import net.pengcook.android.presentation.core.model.Recipe +import net.pengcook.android.presentation.core.util.Event class DetailRecipeViewModel( private val recipe: Recipe, -) : ViewModel() +) : ViewModel() { + private val _navigateToStepEvent = MutableLiveData>() + val navigateToStepEvent: LiveData> get() = _navigateToStepEvent + + fun onNavigateToMakingStep() { + _navigateToStepEvent.value = Event(true) + } +} diff --git a/android/app/src/main/java/net/pengcook/android/presentation/home/FeedRecyclerViewAdapter.kt b/android/app/src/main/java/net/pengcook/android/presentation/home/FeedRecyclerViewAdapter.kt index cc49fa94..e14d991a 100644 --- a/android/app/src/main/java/net/pengcook/android/presentation/home/FeedRecyclerViewAdapter.kt +++ b/android/app/src/main/java/net/pengcook/android/presentation/home/FeedRecyclerViewAdapter.kt @@ -4,24 +4,24 @@ import android.view.LayoutInflater import android.view.ViewGroup import androidx.paging.PagingDataAdapter import androidx.recyclerview.widget.DiffUtil -import androidx.recyclerview.widget.RecyclerView import net.pengcook.android.databinding.ItemFeedBinding import net.pengcook.android.presentation.core.model.Recipe +import net.pengcook.android.presentation.home.holder.FeedViewHolder import net.pengcook.android.presentation.home.listener.FeedItemEventListener class FeedRecyclerViewAdapter(private val eventListener: FeedItemEventListener) : - PagingDataAdapter(diffCallback) { + PagingDataAdapter(diffCallback) { override fun onCreateViewHolder( parent: ViewGroup, viewType: Int, - ): ViewHolder { + ): FeedViewHolder { val inflater = LayoutInflater.from(parent.context) val binding = ItemFeedBinding.inflate(inflater, parent, false) - return ViewHolder(binding) + return FeedViewHolder(binding, eventListener) } override fun onBindViewHolder( - holder: ViewHolder, + holder: FeedViewHolder, position: Int, ) { val item = getItem(position) @@ -30,13 +30,6 @@ class FeedRecyclerViewAdapter(private val eventListener: FeedItemEventListener) } } - class ViewHolder(private val binding: ItemFeedBinding) : RecyclerView.ViewHolder(binding.root) { - fun bind(item: Recipe) { - binding.recipe = item - binding.executePendingBindings() - } - } - companion object { val diffCallback = object : DiffUtil.ItemCallback() { diff --git a/android/app/src/main/java/net/pengcook/android/presentation/home/HomeFragment.kt b/android/app/src/main/java/net/pengcook/android/presentation/home/HomeFragment.kt index e3c6682f..058109fe 100644 --- a/android/app/src/main/java/net/pengcook/android/presentation/home/HomeFragment.kt +++ b/android/app/src/main/java/net/pengcook/android/presentation/home/HomeFragment.kt @@ -7,6 +7,7 @@ import android.view.ViewGroup import androidx.fragment.app.Fragment import androidx.fragment.app.viewModels import androidx.lifecycle.lifecycleScope +import androidx.navigation.fragment.findNavController import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.launch import kotlinx.coroutines.withContext @@ -15,6 +16,7 @@ import net.pengcook.android.data.remote.api.FeedService import net.pengcook.android.data.repository.feed.DefaultFeedRepository import net.pengcook.android.data.util.network.RetrofitClient import net.pengcook.android.databinding.FragmentHomeBinding +import net.pengcook.android.presentation.core.model.Recipe class HomeFragment : Fragment() { private val viewModel: HomeViewModel by viewModels { @@ -49,7 +51,23 @@ class HomeFragment : Fragment() { super.onViewCreated(view, savedInstanceState) initBinding() + observing() + } + + private fun observing() { observeFeedData() + observeUiEvent() + } + + private fun observeUiEvent() { + viewModel.uiEvent.observe(viewLifecycleOwner) { event -> + val newEvent = event.getContentIfNotHandled() ?: return@observe + when (newEvent) { + is HomeEvent.NavigateToDetail -> { + onSingleMovieClicked(newEvent.recipe) + } + } + } } private fun observeFeedData() { @@ -62,6 +80,11 @@ class HomeFragment : Fragment() { } } + private fun onSingleMovieClicked(recipe: Recipe) { + val action = HomeFragmentDirections.actionHomeFragmentToDetailRecipeFragment(recipe) + findNavController().navigate(action) + } + private fun initBinding() { binding.lifecycleOwner = viewLifecycleOwner binding.viewModel = viewModel diff --git a/android/app/src/main/java/net/pengcook/android/presentation/home/HomeViewModel.kt b/android/app/src/main/java/net/pengcook/android/presentation/home/HomeViewModel.kt index a67f574a..26ce35ae 100644 --- a/android/app/src/main/java/net/pengcook/android/presentation/home/HomeViewModel.kt +++ b/android/app/src/main/java/net/pengcook/android/presentation/home/HomeViewModel.kt @@ -1,6 +1,7 @@ package net.pengcook.android.presentation.home import androidx.lifecycle.LiveData +import androidx.lifecycle.MutableLiveData import androidx.lifecycle.ViewModel import androidx.lifecycle.viewModelScope import androidx.paging.Pager @@ -11,11 +12,16 @@ import androidx.paging.liveData import net.pengcook.android.data.datasource.FeedPagingSource import net.pengcook.android.data.repository.feed.FeedRepository import net.pengcook.android.presentation.core.model.Recipe +import net.pengcook.android.presentation.core.util.Event import net.pengcook.android.presentation.home.listener.FeedItemEventListener class HomeViewModel( private val feedRepository: FeedRepository, ) : ViewModel(), FeedItemEventListener { + private val _uiEvent: MutableLiveData> = MutableLiveData() + val uiEvent: LiveData> + get() = _uiEvent + val feedData: LiveData> = Pager( config = PagingConfig(pageSize = PAGE_SIZE, enablePlaceholders = false), @@ -24,11 +30,15 @@ class HomeViewModel( .liveData .cachedIn(viewModelScope) + override fun onNavigateToDetail(recipe: Recipe) { + _uiEvent.value = Event(HomeEvent.NavigateToDetail(recipe)) + } + companion object { private const val PAGE_SIZE = 10 } +} - override fun onNavigateToDetail(recipe: Recipe) { - // Navigate to detail page - } +sealed interface HomeEvent { + data class NavigateToDetail(val recipe: Recipe) : HomeEvent } diff --git a/android/app/src/main/java/net/pengcook/android/presentation/home/holder/FeedViewHolder.kt b/android/app/src/main/java/net/pengcook/android/presentation/home/holder/FeedViewHolder.kt new file mode 100644 index 00000000..03aaa589 --- /dev/null +++ b/android/app/src/main/java/net/pengcook/android/presentation/home/holder/FeedViewHolder.kt @@ -0,0 +1,17 @@ +package net.pengcook.android.presentation.home.holder + +import androidx.recyclerview.widget.RecyclerView +import net.pengcook.android.databinding.ItemFeedBinding +import net.pengcook.android.presentation.core.model.Recipe +import net.pengcook.android.presentation.home.listener.FeedItemEventListener + +class FeedViewHolder( + private val binding: ItemFeedBinding, + private val eventListener: FeedItemEventListener, +) : RecyclerView.ViewHolder(binding.root) { + fun bind(item: Recipe) { + binding.recipe = item + binding.eventListener = eventListener + binding.executePendingBindings() + } +} diff --git a/android/app/src/main/java/net/pengcook/android/presentation/making/RecipeMakingFragment.kt b/android/app/src/main/java/net/pengcook/android/presentation/making/RecipeMakingFragment.kt index bb908b4c..3b11b25b 100644 --- a/android/app/src/main/java/net/pengcook/android/presentation/making/RecipeMakingFragment.kt +++ b/android/app/src/main/java/net/pengcook/android/presentation/making/RecipeMakingFragment.kt @@ -6,6 +6,7 @@ import android.view.View import android.view.ViewGroup import androidx.fragment.app.Fragment import androidx.fragment.app.viewModels +import androidx.navigation.fragment.findNavController import net.pengcook.android.databinding.FragmentRecipeMakingBinding class RecipeMakingFragment : Fragment() { @@ -29,6 +30,12 @@ class RecipeMakingFragment : Fragment() { ) { super.onViewCreated(view, savedInstanceState) initBinding() + viewModel.uiEvent.observe(viewLifecycleOwner) { event -> + val newEvent = event.getContentIfNotHandled() ?: return@observe + when (newEvent) { + is MakingEvent.NavigateToMakingStep -> onNextClicked() + } + } } override fun onDestroyView() { @@ -36,6 +43,11 @@ class RecipeMakingFragment : Fragment() { _binding = null } + private fun onNextClicked() { + val action = RecipeMakingFragmentDirections.actionRecipeMakingFragmentToStepMakingFragment() + findNavController().navigate(action) + } + private fun initBinding() { binding.lifecycleOwner = this binding.vm = viewModel diff --git a/android/app/src/main/java/net/pengcook/android/presentation/making/RecipeMakingViewModel.kt b/android/app/src/main/java/net/pengcook/android/presentation/making/RecipeMakingViewModel.kt index 92bfe13c..1bf1d1bf 100644 --- a/android/app/src/main/java/net/pengcook/android/presentation/making/RecipeMakingViewModel.kt +++ b/android/app/src/main/java/net/pengcook/android/presentation/making/RecipeMakingViewModel.kt @@ -1,10 +1,16 @@ package net.pengcook.android.presentation.making +import androidx.lifecycle.LiveData import androidx.lifecycle.MutableLiveData import androidx.lifecycle.ViewModel +import net.pengcook.android.presentation.core.util.Event import net.pengcook.android.presentation.making.listener.RecipeMakingEventListener class RecipeMakingViewModel : ViewModel(), RecipeMakingEventListener { + private val _uiEvent: MutableLiveData> = MutableLiveData() + val uiEvent: LiveData> + get() = _uiEvent + val titleContent = MutableLiveData() val categorySelectedValue = MutableLiveData() @@ -15,7 +21,11 @@ class RecipeMakingViewModel : ViewModel(), RecipeMakingEventListener { val introductionContent = MutableLiveData() - override fun onNavigateToStep() { - // TODO: Implement navigation logic + override fun onNavigateToMakingStep() { + _uiEvent.value = Event(MakingEvent.NavigateToMakingStep) } } + +sealed interface MakingEvent { + data object NavigateToMakingStep : MakingEvent +} diff --git a/android/app/src/main/java/net/pengcook/android/presentation/making/listener/RecipeMakingEventListener.kt b/android/app/src/main/java/net/pengcook/android/presentation/making/listener/RecipeMakingEventListener.kt index 3d00d454..dd905377 100644 --- a/android/app/src/main/java/net/pengcook/android/presentation/making/listener/RecipeMakingEventListener.kt +++ b/android/app/src/main/java/net/pengcook/android/presentation/making/listener/RecipeMakingEventListener.kt @@ -1,5 +1,5 @@ package net.pengcook.android.presentation.making.listener interface RecipeMakingEventListener { - fun onNavigateToStep() + fun onNavigateToMakingStep() } diff --git a/android/app/src/main/java/net/pengcook/android/presentation/step/RecipeStepFragment.kt b/android/app/src/main/java/net/pengcook/android/presentation/step/RecipeStepFragment.kt index 013185a4..ce359213 100644 --- a/android/app/src/main/java/net/pengcook/android/presentation/step/RecipeStepFragment.kt +++ b/android/app/src/main/java/net/pengcook/android/presentation/step/RecipeStepFragment.kt @@ -70,14 +70,4 @@ class RecipeStepFragment : Fragment() { super.onDestroyView() _binding = null } - - companion object { - fun newInstance(recipeId: Long): RecipeStepFragment { - val fragment = RecipeStepFragment() - val args = Bundle() - args.putLong("recipeId", recipeId) - fragment.arguments = args - return fragment - } - } } diff --git a/android/app/src/main/res/drawable/ic_plus.xml b/android/app/src/main/res/drawable/ic_add_recipe.xml similarity index 100% rename from android/app/src/main/res/drawable/ic_plus.xml rename to android/app/src/main/res/drawable/ic_add_recipe.xml diff --git a/android/app/src/main/res/layout/fragment_detail_recipe.xml b/android/app/src/main/res/layout/fragment_detail_recipe.xml index 6b5f81b3..054d6680 100644 --- a/android/app/src/main/res/layout/fragment_detail_recipe.xml +++ b/android/app/src/main/res/layout/fragment_detail_recipe.xml @@ -8,6 +8,10 @@ + + diff --git a/android/app/src/main/res/layout/fragment_recipe_making.xml b/android/app/src/main/res/layout/fragment_recipe_making.xml index d785d288..b230af10 100644 --- a/android/app/src/main/res/layout/fragment_recipe_making.xml +++ b/android/app/src/main/res/layout/fragment_recipe_making.xml @@ -147,6 +147,7 @@ android:backgroundTint="@color/gray_300" android:text="@string/making_next" android:textColor="@color/black_100" + android:onClick="@{() -> vm.onNavigateToMakingStep()}" android:textStyle="bold" app:layout_constraintBottom_toBottomOf="parent" app:layout_constraintEnd_toEndOf="parent" diff --git a/android/app/src/main/res/layout/item_feed.xml b/android/app/src/main/res/layout/item_feed.xml index 234ebf02..8fcab5fc 100644 --- a/android/app/src/main/res/layout/item_feed.xml +++ b/android/app/src/main/res/layout/item_feed.xml @@ -8,12 +8,17 @@ + + + android:layout_marginBottom="12dp" + android:onClick="@{() -> eventListener.onNavigateToDetail(recipe)}"> + app:layout_constraintTop_toTopOf="parent" /> + app:shapeAppearanceOverlay="@style/roundImageView" + tools:src="@drawable/ic_launcher_background" /> + app:layout_constraintStart_toEndOf="@id/profile_image" + app:layout_constraintTop_toTopOf="@id/profile_image" + tools:text="Username" /> + android:src="@drawable/ic_menu_dots" + app:layout_constraintBottom_toBottomOf="@id/profile_image" + app:layout_constraintEnd_toEndOf="parent" + app:layout_constraintTop_toTopOf="@id/profile_image" /> @@ -96,11 +101,11 @@ android:layout_width="24dp" android:layout_height="24dp" android:layout_marginTop="8dp" + android:layout_marginBottom="@dimen/item_feed_padding" android:src="@drawable/ic_heart" - app:layout_constraintStart_toStartOf="@id/recipe_title" - app:layout_constraintTop_toBottomOf="@id/recipe_title" app:layout_constraintBottom_toBottomOf="parent" - android:layout_marginBottom="@dimen/item_feed_padding"/> + app:layout_constraintStart_toStartOf="@id/recipe_title" + app:layout_constraintTop_toBottomOf="@id/recipe_title" /> + tools:text="1200" /> + tools:text="800" /> diff --git a/android/app/src/main/res/menu/bottom_nav_menu.xml b/android/app/src/main/res/menu/bottom_nav_menu.xml index 745fe410..72ce5073 100644 --- a/android/app/src/main/res/menu/bottom_nav_menu.xml +++ b/android/app/src/main/res/menu/bottom_nav_menu.xml @@ -12,8 +12,8 @@ android:title="Search" /> + tools:layout="@layout/fragment_home"> + + + + + + tools:layout="@layout/fragment_detail_recipe"> + + + + tools:layout="@layout/fragment_category"> + + + + + tools:layout="@layout/fragment_search"> + + + + + + + + + android:label="RecipeMakingFragment" > + + - diff --git a/android/build.gradle.kts b/android/build.gradle.kts index 6b29c71c..c737204e 100644 --- a/android/build.gradle.kts +++ b/android/build.gradle.kts @@ -8,3 +8,8 @@ plugins { allprojects { apply(plugin = "org.jlleitschuh.gradle.ktlint") } +buildscript { + dependencies { + classpath("androidx.navigation:navigation-safe-args-gradle-plugin:2.7.7") + } +}