@@ -2,154 +2,105 @@ package com.project200.feature.timer.custom
22
33import android.content.Context
44import android.os.Bundle
5+ import android.view.LayoutInflater
56import android.view.View
7+ import android.view.ViewGroup
68import android.view.inputmethod.InputMethodManager
79import android.widget.Toast
8- import androidx.core.widget.addTextChangedListener
10+ import androidx.compose.runtime.getValue
11+ import androidx.compose.ui.platform.ComposeView
12+ import androidx.fragment.app.Fragment
913import androidx.fragment.app.viewModels
14+ import androidx.lifecycle.Lifecycle
15+ import androidx.lifecycle.compose.collectAsStateWithLifecycle
16+ import androidx.lifecycle.lifecycleScope
17+ import androidx.lifecycle.repeatOnLifecycle
1018import androidx.navigation.fragment.findNavController
1119import androidx.navigation.fragment.navArgs
12- import androidx.recyclerview.widget.ItemTouchHelper
13- import androidx.recyclerview.widget.LinearLayoutManager
14- import androidx.recyclerview.widget.RecyclerView
1520import com.project200.feature.timer.TimePickerDialog
16- import com.project200.feature.timer.custom.adapter.AddedStepRVAdapter
17- import com.project200.feature.timer.custom.adapter.OnStepItemClickListener
18- import com.project200.feature.timer.custom.adapter.StepItemMoveCallback
19- import com.project200.presentation.base.BindingFragment
21+ import com.project200.presentation.compose.applyAppTheme
2022import com.project200.undabang.feature.timer.R
21- import com.project200.undabang.feature.timer.databinding.FragmentCustomTimerFormBinding
2223import dagger.hilt.android.AndroidEntryPoint
24+ import kotlinx.coroutines.launch
2325
2426@AndroidEntryPoint
25- class CustomTimerFormFragment : BindingFragment < FragmentCustomTimerFormBinding >( R .layout.fragment_custom_timer_form ) {
27+ class CustomTimerFormFragment : Fragment ( ) {
2628 private val viewModel: CustomTimerFormViewModel by viewModels()
27- private lateinit var stepAdapter: AddedStepRVAdapter
28- private lateinit var itemTouchHelper: ItemTouchHelper
2929 private val args: CustomTimerFormFragmentArgs by navArgs()
3030
31- override fun getViewBinding (view : View ): FragmentCustomTimerFormBinding {
32- return FragmentCustomTimerFormBinding .bind(view)
33- }
34-
3531 override fun onCreate (savedInstanceState : Bundle ? ) {
3632 super .onCreate(savedInstanceState)
3733 viewModel.loadData(args.customTimerId)
3834 }
3935
40- override fun setupViews () {
41- binding.baseToolbar.apply {
42- setTitle(getString(if (viewModel.isEditMode) R .string.custom_timer_edit else R .string.custom_timer_create))
43- showBackButton(true ) { findNavController().navigateUp() }
44- }
45- initRecyclerView()
46- initListeners()
47- }
48-
49- private fun initListeners () {
50- binding.timerTitleEt.addTextChangedListener { title ->
51- viewModel.updateTimerTitle(title.toString())
52- }
53-
54- binding.completeBtn.setOnClickListener {
55- clearFocusAndHideKeyboard()
56- viewModel.submitCustomTimer()
57- }
58- }
59-
60- private fun initRecyclerView () {
61- val itemTouchHelperCallback =
62- StepItemMoveCallback { from, to ->
63- viewModel.moveStep(from, to)
36+ override fun onCreateView (
37+ inflater : LayoutInflater ,
38+ container : ViewGroup ? ,
39+ savedInstanceState : Bundle ? ,
40+ ): View =
41+ ComposeView (requireContext()).apply {
42+ applyAppTheme {
43+ val state by viewModel.uiState.collectAsStateWithLifecycle()
44+ CustomTimerFormScreen (
45+ title = state.title,
46+ listItems = state.listItems,
47+ isEditMode = viewModel.isEditMode,
48+ onTitleChange = viewModel::updateTimerTitle,
49+ onStepNameChange = viewModel::updateStepName,
50+ onStepTimeClick = { id, time -> showTimePickerDialog(id, time) },
51+ onStepDelete = viewModel::removeStep,
52+ onMove = viewModel::moveStep,
53+ onNewStepNameChange = viewModel::updateNewStepName,
54+ onNewStepTimeClick = { time -> showTimePickerDialog(null , time) },
55+ onAddStep = viewModel::addStep,
56+ onCompleteClick = {
57+ clearFocusAndHideKeyboard()
58+ viewModel.submitCustomTimer()
59+ },
60+ onBackClick = { findNavController().navigateUp() },
61+ )
6462 }
65- itemTouchHelper = ItemTouchHelper (itemTouchHelperCallback)
66-
67- stepAdapter =
68- AddedStepRVAdapter (
69- object : OnStepItemClickListener {
70- // 스텝 아이템 이벤트
71- override fun onDeleteClick (id : Long ) {
72- viewModel.removeStep(id)
73- }
74-
75- override fun onTimeClick (
76- id : Long ,
77- time : Int ,
78- ) {
79- showTimePickerDialog(id, time)
80- }
81-
82- override fun onStepNameChanged (
83- id : Long ,
84- name : String ,
85- ) {
86- viewModel.updateStepName(id, name)
87- }
88-
89- override fun onStartDrag (viewHolder : RecyclerView .ViewHolder ) {
90- itemTouchHelper.startDrag(viewHolder)
91- }
92-
93- // 입력 필드 이벤트
94- override fun onNewStepNameChanged (name : String ) {
95- viewModel.updateNewStepName(name)
96- }
97-
98- override fun onNewStepTimeClick (time : Int ) {
99- showTimePickerDialog(null , time)
100- }
101-
102- override fun onAddStepClick () {
103- viewModel.addStep()
104- }
105- },
106- )
107-
108- binding.stepRv.apply {
109- adapter = stepAdapter
110- layoutManager = LinearLayoutManager (requireContext())
111- itemTouchHelper.attachToRecyclerView(this )
112- }
113- }
114-
115- override fun setupObservers () {
116- viewModel.uiState.observe(viewLifecycleOwner) { state ->
117- if (state == null ) return @observe
118-
119- if (binding.timerTitleEt.text.toString() != state.title) {
120- binding.timerTitleEt.setText(state.title)
121- }
122- stepAdapter.submitList(state.listItems)
12363 }
12464
125- viewModel.toast.observe(viewLifecycleOwner) { toast ->
126- val messageResId =
127- when (toast) {
128- // 검증 결과에 따른 메시지 매핑
129- ToastMessageType .EMPTY_TITLE -> R .string.custom_timer_error_empty_title
130- ToastMessageType .NO_STEPS -> R .string.custom_timer_error_no_steps
131- ToastMessageType .INVALID_STEP_TIME -> R .string.custom_timer_error_invalid_time
132- ToastMessageType .MAX_STEPS -> R .string.custom_timer_error_max_steps
133- ToastMessageType .NO_CHANGES -> R .string.custom_timer_error_no_changes
134- ToastMessageType .EMPTY_STEP_NAME -> R .string.custom_timer_error_empty_step_name
135- // API 에러 메시지 매핑
136- ToastMessageType .CREATE_ERROR -> R .string.custom_timer_error_create_failed
137- ToastMessageType .EDIT_ERROR -> R .string.custom_timer_error_edit_failed
138- ToastMessageType .GET_ERROR -> R .string.error_failed_to_load_list
139- ToastMessageType .UNKNOWN_ERROR -> R .string.unknown_error
140- null -> return @observe
65+ override fun onViewCreated (
66+ view : View ,
67+ savedInstanceState : Bundle ? ,
68+ ) {
69+ super .onViewCreated(view, savedInstanceState)
70+
71+ viewLifecycleOwner.lifecycleScope.launch {
72+ repeatOnLifecycle(Lifecycle .State .STARTED ) {
73+ launch {
74+ viewModel.toast.collect { type ->
75+ val messageResId =
76+ when (type) {
77+ ToastMessageType .EMPTY_TITLE -> R .string.custom_timer_error_empty_title
78+ ToastMessageType .NO_STEPS -> R .string.custom_timer_error_no_steps
79+ ToastMessageType .INVALID_STEP_TIME -> R .string.custom_timer_error_invalid_time
80+ ToastMessageType .MAX_STEPS -> R .string.custom_timer_error_max_steps
81+ ToastMessageType .NO_CHANGES -> R .string.custom_timer_error_no_changes
82+ ToastMessageType .EMPTY_STEP_NAME -> R .string.custom_timer_error_empty_step_name
83+ ToastMessageType .CREATE_ERROR -> R .string.custom_timer_error_create_failed
84+ ToastMessageType .EDIT_ERROR -> R .string.custom_timer_error_edit_failed
85+ ToastMessageType .GET_ERROR -> R .string.error_failed_to_load_list
86+ ToastMessageType .UNKNOWN_ERROR -> R .string.unknown_error
87+ }
88+ Toast .makeText(requireContext(), messageResId, Toast .LENGTH_SHORT ).show()
89+ }
14190 }
142- Toast .makeText(requireContext(), messageResId, Toast .LENGTH_SHORT ).show()
143- }
144-
145- viewModel.submitResult.observe(viewLifecycleOwner) { id ->
146- if (id != null && findNavController().currentDestination?.id == R .id.customTimerFormFragment) {
147- if (viewModel.isEditMode) {
148- findNavController().navigateUp()
149- } else {
150- findNavController().navigate(
151- CustomTimerFormFragmentDirections .actionCustomTimerFormFragmentToCustomTimerFragment(id),
152- )
91+ launch {
92+ viewModel.submitResult.collect { id ->
93+ if (findNavController().currentDestination?.id != R .id.customTimerFormFragment) {
94+ return @collect
95+ }
96+ if (viewModel.isEditMode) {
97+ findNavController().navigateUp()
98+ } else {
99+ findNavController().navigate(
100+ CustomTimerFormFragmentDirections .actionCustomTimerFormFragmentToCustomTimerFragment(id),
101+ )
102+ }
103+ }
153104 }
154105 }
155106 }
@@ -166,21 +117,16 @@ class CustomTimerFormFragment : BindingFragment<FragmentCustomTimerFormBinding>(
166117 Toast .makeText(requireContext(), R .string.custom_timer_error_invalid_time, Toast .LENGTH_SHORT ).show()
167118 return @TimePickerDialog
168119 }
169- id?.let { viewModel.updateStepTime(id, newTimeInSeconds) } ? : run {
170- // id가 null인 경우는 새 스텝을 추가할 때이므로 새 스텝 시간 업데이트
171- viewModel.updateNewStepTime(newTimeInSeconds)
172- }
120+ id?.let { viewModel.updateStepTime(it, newTimeInSeconds) }
121+ ? : viewModel.updateNewStepTime(newTimeInSeconds)
173122 },
174123 ).show(parentFragmentManager, this ::class .java.simpleName)
175124 }
176125
177- // 포커스 해제 및 키보드 숨기기
178126 private fun clearFocusAndHideKeyboard () {
179- val currentFocusView = activity?.currentFocus
180- if (currentFocusView != null ) {
181- currentFocusView.clearFocus()
182- val imm = activity?.getSystemService(Context .INPUT_METHOD_SERVICE ) as ? InputMethodManager
183- imm?.hideSoftInputFromWindow(currentFocusView.windowToken, 0 )
184- }
127+ val current = activity?.currentFocus ? : return
128+ current.clearFocus()
129+ val imm = activity?.getSystemService(Context .INPUT_METHOD_SERVICE ) as ? InputMethodManager
130+ imm?.hideSoftInputFromWindow(current.windowToken, 0 )
185131 }
186132}
0 commit comments