diff --git a/android/app/src/main/java/net/pengcook/android/presentation/BindingAdapters.kt b/android/app/src/main/java/net/pengcook/android/presentation/BindingAdapters.kt index c12cd5f0..34fdf149 100644 --- a/android/app/src/main/java/net/pengcook/android/presentation/BindingAdapters.kt +++ b/android/app/src/main/java/net/pengcook/android/presentation/BindingAdapters.kt @@ -1,5 +1,6 @@ package net.pengcook.android.presentation +import android.net.Uri import android.view.View import android.widget.AdapterView import android.widget.ArrayAdapter @@ -25,6 +26,19 @@ fun loadImage( } } +@BindingAdapter("app:imageUri") +fun loadImage( + view: ImageView, + uri: Uri?, +) { + if (uri != null) { + Glide + .with(view.context) + .load(uri) + .into(view) + } +} + @BindingAdapter("app:favoriteCount") fun favoriteCountText( view: TextView, diff --git a/android/app/src/main/java/net/pengcook/android/presentation/core/listener/BottomButtonClickListener.kt b/android/app/src/main/java/net/pengcook/android/presentation/core/listener/BottomButtonClickListener.kt deleted file mode 100644 index f2cc97ad..00000000 --- a/android/app/src/main/java/net/pengcook/android/presentation/core/listener/BottomButtonClickListener.kt +++ /dev/null @@ -1,5 +0,0 @@ -package net.pengcook.android.presentation.core.listener - -interface BottomButtonClickListener { - fun onClick() -} diff --git a/android/app/src/main/java/net/pengcook/android/presentation/core/util/Event.kt b/android/app/src/main/java/net/pengcook/android/presentation/core/util/Event.kt new file mode 100644 index 00000000..f449274c --- /dev/null +++ b/android/app/src/main/java/net/pengcook/android/presentation/core/util/Event.kt @@ -0,0 +1,17 @@ +package net.pengcook.android.presentation.core.util + +open class Event(private val content: T) { + var hasBeenHandled = false + private set + + fun getContentIfNotHandled(): T? { + return if (hasBeenHandled) { + null + } else { + hasBeenHandled = true + content + } + } + + fun peekContent(): T = content +} diff --git a/android/app/src/main/java/net/pengcook/android/presentation/signup/BottomButtonClickListener.kt b/android/app/src/main/java/net/pengcook/android/presentation/signup/BottomButtonClickListener.kt new file mode 100644 index 00000000..ba374570 --- /dev/null +++ b/android/app/src/main/java/net/pengcook/android/presentation/signup/BottomButtonClickListener.kt @@ -0,0 +1,5 @@ +package net.pengcook.android.presentation.signup + +interface BottomButtonClickListener { + fun onConfirm() +} diff --git a/android/app/src/main/java/net/pengcook/android/presentation/signup/SignUpEvent.kt b/android/app/src/main/java/net/pengcook/android/presentation/signup/SignUpEvent.kt new file mode 100644 index 00000000..44b20f9f --- /dev/null +++ b/android/app/src/main/java/net/pengcook/android/presentation/signup/SignUpEvent.kt @@ -0,0 +1,14 @@ +package net.pengcook.android.presentation.signup + +import java.io.File + +sealed interface SignUpEvent { + class NavigateToMain( + val accessToken: String, + val refreshToken: String, + ) : SignUpEvent + + data object Error : SignUpEvent + + data object NavigateBack : SignUpEvent +} diff --git a/android/app/src/main/java/net/pengcook/android/presentation/signup/SignUpForm.kt b/android/app/src/main/java/net/pengcook/android/presentation/signup/SignUpForm.kt new file mode 100644 index 00000000..7da3b707 --- /dev/null +++ b/android/app/src/main/java/net/pengcook/android/presentation/signup/SignUpForm.kt @@ -0,0 +1,21 @@ +package net.pengcook.android.presentation.signup + +import java.io.File + +data class SignUpForm( + val profileImage: File? = null, + val username: String = INITIAL_VALUE, + val nickname: String = INITIAL_VALUE, + val yearOfBirthday: Int = INITIAL_YEAR, + val monthOfBirthday: Int = INITIAL_MONTH, + val dayOfBirthday: Int = INITIAL_DAY, + val country: String = INITIAL_COUNTRY, +) { + companion object { + private const val INITIAL_VALUE = "" + private const val INITIAL_YEAR = 2024 + private const val INITIAL_MONTH = 1 + private const val INITIAL_DAY = 1 + private const val INITIAL_COUNTRY = "" + } +} diff --git a/android/app/src/main/java/net/pengcook/android/presentation/signup/SignUpFormChangeListener.kt b/android/app/src/main/java/net/pengcook/android/presentation/signup/SignUpFormChangeListener.kt new file mode 100644 index 00000000..f6d04622 --- /dev/null +++ b/android/app/src/main/java/net/pengcook/android/presentation/signup/SignUpFormChangeListener.kt @@ -0,0 +1,19 @@ +package net.pengcook.android.presentation.signup + +import java.io.File + +interface SignUpFormEventListener { + fun onProfileImageChange(image: File) + + fun onUsernameChange(username: String) + + fun onNicknameChange(nickname: String) + + fun onYearOfBirthChange(year: Int) + + fun onMonthOfBirthChange(month: Int) + + fun onDayOfBirthChange(day: Int) + + fun onCountryChange(country: String) +} diff --git a/android/app/src/main/java/net/pengcook/android/presentation/signup/SignUpFragment.kt b/android/app/src/main/java/net/pengcook/android/presentation/signup/SignUpFragment.kt new file mode 100644 index 00000000..bccd0f67 --- /dev/null +++ b/android/app/src/main/java/net/pengcook/android/presentation/signup/SignUpFragment.kt @@ -0,0 +1,120 @@ +package net.pengcook.android.presentation.signup + +import android.net.Uri +import android.os.Bundle +import android.view.LayoutInflater +import android.view.View +import android.view.ViewGroup +import android.widget.ArrayAdapter +import androidx.activity.result.contract.ActivityResultContracts +import androidx.fragment.app.Fragment +import androidx.fragment.app.viewModels +import androidx.navigation.fragment.findNavController +import net.pengcook.android.R +import net.pengcook.android.databinding.FragmentSignUpBinding + +class SignUpFragment : Fragment() { + private var _binding: FragmentSignUpBinding? = null + private val binding: FragmentSignUpBinding + get() = _binding!! + private val viewModel: SignUpViewModel by viewModels() + private val launcher = + registerForActivityResult(ActivityResultContracts.GetContent()) { uri -> + try { + viewModel.changeProfileImage(uri) + } catch (e: RuntimeException) { + + } + } + + override fun onCreateView( + inflater: LayoutInflater, + container: ViewGroup?, + savedInstanceState: Bundle?, + ): View { + _binding = FragmentSignUpBinding.inflate(inflater) + binding.viewModel = viewModel + binding.lifecycleOwner = this + return binding.root + } + + override fun onViewCreated( + view: View, + savedInstanceState: Bundle?, + ) { + super.onViewCreated(view, savedInstanceState) + setUpBirthDateSpinner() + setUpCountrySpinner() + setUpClickListener() + observeViewModel() + } + + private fun setUpClickListener() { + binding.ivProfileImage.setOnClickListener { + launcher.launch("image/*") + } + } + + override fun onDestroy() { + super.onDestroy() + _binding = null + } + + private fun setUpCountrySpinner() { + val countryAdapter = + ArrayAdapter( + requireContext(), + android.R.layout.simple_spinner_item, + List(100) { "country$it" }, + ) + countryAdapter.setDropDownViewResource(android.R.layout.simple_spinner_dropdown_item) + binding.formCountry.spFormContent.spDefault.adapter = countryAdapter + } + + private fun setUpBirthDateSpinner() { + val yearAdapter = + ArrayAdapter( + requireContext(), + android.R.layout.simple_spinner_item, + List(100) { (it + 1820).toString() }, + ) + yearAdapter.setDropDownViewResource(android.R.layout.simple_spinner_dropdown_item) + binding.formBirthDate.spFormContent1.spDefault.adapter = yearAdapter + + val monthAdapter = + ArrayAdapter( + requireContext(), + android.R.layout.simple_spinner_item, + List(12) { (it + 1).toString() }, + ) + monthAdapter.setDropDownViewResource(android.R.layout.simple_spinner_dropdown_item) + binding.formBirthDate.spFormContent2.spDefault.adapter = monthAdapter + + val dayAdapter = + ArrayAdapter( + requireContext(), + android.R.layout.simple_spinner_item, + List(31) { (it + 1).toString() }, + ) + dayAdapter.setDropDownViewResource(android.R.layout.simple_spinner_dropdown_item) + binding.formBirthDate.spFormContent3.spDefault.adapter = dayAdapter + } + + private fun observeViewModel() { + viewModel.signUpEvent.observe(viewLifecycleOwner) { event -> + val signUpEvent = event.getContentIfNotHandled() ?: return@observe + when (signUpEvent) { + is SignUpEvent.NavigateToMain -> { + findNavController().navigate(R.id.action_signUpFragment_to_homeFragment) + } + is SignUpEvent.Error -> { + + } + is SignUpEvent.NavigateBack -> { + findNavController().navigate(R.id.action_signUpFragment_to_onboardingFragment) + } + } + } + } +} + diff --git a/android/app/src/main/java/net/pengcook/android/presentation/signup/SignUpUiState.kt b/android/app/src/main/java/net/pengcook/android/presentation/signup/SignUpUiState.kt new file mode 100644 index 00000000..70eaf362 --- /dev/null +++ b/android/app/src/main/java/net/pengcook/android/presentation/signup/SignUpUiState.kt @@ -0,0 +1,9 @@ +package net.pengcook.android.presentation.signup + +import android.net.Uri + +data class SignUpUiState( + val isLoading: Boolean = false, + val profileImage: Uri? = null, + val fulfilled: Boolean = false, +) diff --git a/android/app/src/main/java/net/pengcook/android/presentation/signup/SignUpViewModel.kt b/android/app/src/main/java/net/pengcook/android/presentation/signup/SignUpViewModel.kt new file mode 100644 index 00000000..af99430e --- /dev/null +++ b/android/app/src/main/java/net/pengcook/android/presentation/signup/SignUpViewModel.kt @@ -0,0 +1,39 @@ +package net.pengcook.android.presentation.signup + +import android.net.Uri +import androidx.lifecycle.LiveData +import androidx.lifecycle.MutableLiveData +import androidx.lifecycle.ViewModel +import net.pengcook.android.presentation.core.util.Event + +class SignUpViewModel : + ViewModel(), + BottomButtonClickListener { + var usernameContent: MutableLiveData = MutableLiveData("") + var nicknameContent: MutableLiveData = MutableLiveData("") + var year: MutableLiveData = MutableLiveData("") + var month: MutableLiveData = MutableLiveData("") + var day: MutableLiveData = MutableLiveData("") + var country: MutableLiveData = MutableLiveData("") + private var _imageUri: MutableLiveData = MutableLiveData() + val imageUri: LiveData + get() = _imageUri + + private var _signUpUiState: MutableLiveData = MutableLiveData(SignUpUiState()) + val signUpUiState: LiveData + get() = _signUpUiState + + private var _signUpEvent: MutableLiveData> = MutableLiveData() + val signUpEvent: LiveData> + get() = _signUpEvent + + fun changeProfileImage(uri: Uri) { + _imageUri.value = uri + } + + override fun onConfirm() { + _signUpUiState.value = signUpUiState.value?.copy(isLoading = true) + _signUpEvent.value = Event(SignUpEvent.NavigateToMain("", "")) + _signUpUiState.value = signUpUiState.value?.copy(isLoading = false) + } +} diff --git a/android/app/src/main/res/layout/fragment_sign_up.xml b/android/app/src/main/res/layout/fragment_sign_up.xml new file mode 100644 index 00000000..1e15fbcf --- /dev/null +++ b/android/app/src/main/res/layout/fragment_sign_up.xml @@ -0,0 +1,115 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/android/app/src/main/res/layout/item_button_bottom.xml b/android/app/src/main/res/layout/item_button_bottom.xml index ea2d251c..86b55abf 100644 --- a/android/app/src/main/res/layout/item_button_bottom.xml +++ b/android/app/src/main/res/layout/item_button_bottom.xml @@ -9,8 +9,12 @@ type="String" /> + name="enabled" + type="Boolean" /> + + diff --git a/android/app/src/main/res/navigation/nav_graph.xml b/android/app/src/main/res/navigation/nav_graph.xml index 6df4ad58..11cd932c 100644 --- a/android/app/src/main/res/navigation/nav_graph.xml +++ b/android/app/src/main/res/navigation/nav_graph.xml @@ -17,4 +17,31 @@ android:label="fragment_detail_recipe" tools:layout="@layout/fragment_detail_recipe" /> + + + + + + + + +