From 99b0d4c0bb5ab9e40ac6521b5d2251f668e8d6ff Mon Sep 17 00:00:00 2001 From: Hogu59 Date: Thu, 25 Jul 2024 22:05:32 +0900 Subject: [PATCH] :sparkles: implement RecipeStepFragment --- .../presentation/step/RecipeStepFragment.kt | 83 +++++++++++++++++++ .../step/RecipeStepPagerRecyclerAdapter.kt | 33 ++++++++ .../presentation/step/RecipeStepViewHolder.kt | 16 ++++ .../presentation/step/RecipeStepViewModel.kt | 27 ++++++ .../step/RecipeStepViewModelFactory.kt | 21 +++++ 5 files changed, 180 insertions(+) create mode 100644 android/app/src/main/java/net/pengcook/android/presentation/step/RecipeStepFragment.kt create mode 100644 android/app/src/main/java/net/pengcook/android/presentation/step/RecipeStepPagerRecyclerAdapter.kt create mode 100644 android/app/src/main/java/net/pengcook/android/presentation/step/RecipeStepViewHolder.kt create mode 100644 android/app/src/main/java/net/pengcook/android/presentation/step/RecipeStepViewModel.kt create mode 100644 android/app/src/main/java/net/pengcook/android/presentation/step/RecipeStepViewModelFactory.kt 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 new file mode 100644 index 00000000..013185a4 --- /dev/null +++ b/android/app/src/main/java/net/pengcook/android/presentation/step/RecipeStepFragment.kt @@ -0,0 +1,83 @@ +package net.pengcook.android.presentation.step + +import android.os.Bundle +import android.view.LayoutInflater +import android.view.View +import android.view.ViewGroup +import androidx.fragment.app.Fragment +import androidx.fragment.app.viewModels +import androidx.viewpager2.widget.ViewPager2 +import net.pengcook.android.data.datasource.feed.DefaultFeedRemoteDataSource +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.FragmentRecipeStepBinding + +class RecipeStepFragment : Fragment() { + private val recipeId: Long = 1L + private val viewModel: RecipeStepViewModel by viewModels { + RecipeStepViewModelFactory( + recipeId = recipeId, + feedRepository = + DefaultFeedRepository( + feedRemoteDataSource = + DefaultFeedRemoteDataSource( + RetrofitClient.service( + FeedService::class.java, + ), + ), + ), + ) + } + + private var _binding: FragmentRecipeStepBinding? = null + val binding: FragmentRecipeStepBinding + get() = _binding!! + + private val recipeStepPagerRecyclerAdapter: RecipeStepPagerRecyclerAdapter by lazy { + RecipeStepPagerRecyclerAdapter() + } + + override fun onCreateView( + inflater: LayoutInflater, + container: ViewGroup?, + savedInstanceState: Bundle?, + ): View { + _binding = FragmentRecipeStepBinding.inflate(layoutInflater) + return binding.root + } + + override fun onViewCreated( + view: View, + savedInstanceState: Bundle?, + ) { + super.onViewCreated(view, savedInstanceState) + viewModel.fetchRecipeSteps() + + viewModel.recipeSteps.observe(viewLifecycleOwner) { recipeSteps -> + recipeStepPagerRecyclerAdapter.updateList(recipeSteps) + } + + binding.vpStepRecipe.apply { + adapter = recipeStepPagerRecyclerAdapter + orientation = ViewPager2.ORIENTATION_HORIZONTAL + } + + binding.dotsIndicator.attachTo(binding.vpStepRecipe) + } + + override fun onDestroyView() { + 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/java/net/pengcook/android/presentation/step/RecipeStepPagerRecyclerAdapter.kt b/android/app/src/main/java/net/pengcook/android/presentation/step/RecipeStepPagerRecyclerAdapter.kt new file mode 100644 index 00000000..dbdeb933 --- /dev/null +++ b/android/app/src/main/java/net/pengcook/android/presentation/step/RecipeStepPagerRecyclerAdapter.kt @@ -0,0 +1,33 @@ +package net.pengcook.android.presentation.step + +import android.view.LayoutInflater +import android.view.ViewGroup +import androidx.recyclerview.widget.RecyclerView +import net.pengcook.android.databinding.ItemStepRecipeBinding +import net.pengcook.android.presentation.core.model.RecipeStep + +class RecipeStepPagerRecyclerAdapter( + private var pageList: List = emptyList(), +) : RecyclerView.Adapter() { + override fun onCreateViewHolder( + parent: ViewGroup, + viewType: Int, + ): RecipeStepViewHolder { + val binding = ItemStepRecipeBinding.inflate(LayoutInflater.from(parent.context), parent, false) + return RecipeStepViewHolder(binding) + } + + override fun getItemCount(): Int = pageList.size + + override fun onBindViewHolder( + holder: RecipeStepViewHolder, + position: Int, + ) { + holder.bind(pageList[position]) + } + + fun updateList(list: List) { + pageList = list + notifyItemInserted(pageList.size) + } +} diff --git a/android/app/src/main/java/net/pengcook/android/presentation/step/RecipeStepViewHolder.kt b/android/app/src/main/java/net/pengcook/android/presentation/step/RecipeStepViewHolder.kt new file mode 100644 index 00000000..10bc4641 --- /dev/null +++ b/android/app/src/main/java/net/pengcook/android/presentation/step/RecipeStepViewHolder.kt @@ -0,0 +1,16 @@ +package net.pengcook.android.presentation.step + +import android.os.SystemClock +import androidx.recyclerview.widget.RecyclerView +import net.pengcook.android.databinding.ItemStepRecipeBinding +import net.pengcook.android.presentation.core.model.RecipeStep + +class RecipeStepViewHolder( + private val binding: ItemStepRecipeBinding, +) : RecyclerView.ViewHolder(binding.root) { + fun bind(recipeStep: RecipeStep) { + binding.recipeStep = recipeStep + binding.chronometer.format = "%MM:ss" + binding.chronometer.base = SystemClock.elapsedRealtime() + recipeStep.sequence * 1000 + } +} diff --git a/android/app/src/main/java/net/pengcook/android/presentation/step/RecipeStepViewModel.kt b/android/app/src/main/java/net/pengcook/android/presentation/step/RecipeStepViewModel.kt new file mode 100644 index 00000000..e0b90293 --- /dev/null +++ b/android/app/src/main/java/net/pengcook/android/presentation/step/RecipeStepViewModel.kt @@ -0,0 +1,27 @@ +package net.pengcook.android.presentation.step + +import androidx.lifecycle.LiveData +import androidx.lifecycle.MutableLiveData +import androidx.lifecycle.ViewModel +import androidx.lifecycle.viewModelScope +import kotlinx.coroutines.launch +import net.pengcook.android.data.repository.feed.FeedRepository +import net.pengcook.android.presentation.core.model.RecipeStep + +class RecipeStepViewModel( + private val recipeId: Long, + private val feedRepository: FeedRepository, +) : ViewModel() { + private var _recipeSteps: MutableLiveData> = MutableLiveData(emptyList()) + val recipeSteps: LiveData> + get() = _recipeSteps + + fun fetchRecipeSteps() { + viewModelScope.launch { + val response = feedRepository.fetchRecipeSteps(recipeId) + if (response.isSuccess) { + _recipeSteps.value = response.getOrNull() ?: emptyList() + } + } + } +} diff --git a/android/app/src/main/java/net/pengcook/android/presentation/step/RecipeStepViewModelFactory.kt b/android/app/src/main/java/net/pengcook/android/presentation/step/RecipeStepViewModelFactory.kt new file mode 100644 index 00000000..09190c6f --- /dev/null +++ b/android/app/src/main/java/net/pengcook/android/presentation/step/RecipeStepViewModelFactory.kt @@ -0,0 +1,21 @@ +package net.pengcook.android.presentation.step + +import androidx.lifecycle.ViewModel +import androidx.lifecycle.ViewModelProvider +import net.pengcook.android.data.repository.feed.FeedRepository + +class RecipeStepViewModelFactory( + private val recipeId: Long, + private val feedRepository: FeedRepository, +) : ViewModelProvider.Factory { + override fun create(modelClass: Class): T { + if (modelClass.isAssignableFrom(RecipeStepViewModel::class.java)) { + return RecipeStepViewModel( + recipeId = recipeId, + feedRepository = feedRepository, + ) as T + } else { + throw IllegalArgumentException() + } + } +}