mirror of
https://github.com/shaulascr/ecommerce_serang.git
synced 2025-08-10 09:22:21 +00:00
update addrses in register
This commit is contained in:
@ -4,10 +4,10 @@ import com.google.gson.annotations.SerializedName
|
|||||||
|
|
||||||
data class CreateAddressRequest (
|
data class CreateAddressRequest (
|
||||||
@SerializedName("latitude")
|
@SerializedName("latitude")
|
||||||
val lat: Double,
|
val lat: Double? = null,
|
||||||
|
|
||||||
@SerializedName("longitude")
|
@SerializedName("longitude")
|
||||||
val long: Double,
|
val long: Double? = null,
|
||||||
|
|
||||||
@SerializedName("street")
|
@SerializedName("street")
|
||||||
val street: String,
|
val street: String,
|
||||||
|
@ -1,7 +1,10 @@
|
|||||||
package com.alya.ecommerce_serang.data.api.dto
|
package com.alya.ecommerce_serang.data.api.dto
|
||||||
|
|
||||||
|
import android.os.Parcelable
|
||||||
import com.google.gson.annotations.SerializedName
|
import com.google.gson.annotations.SerializedName
|
||||||
|
import kotlinx.android.parcel.Parcelize
|
||||||
|
|
||||||
|
@Parcelize
|
||||||
data class RegisterRequest (
|
data class RegisterRequest (
|
||||||
val name: String?,
|
val name: String?,
|
||||||
val email: String?,
|
val email: String?,
|
||||||
@ -15,4 +18,4 @@ data class RegisterRequest (
|
|||||||
val image: String? = null,
|
val image: String? = null,
|
||||||
|
|
||||||
val otp: String? = null
|
val otp: String? = null
|
||||||
)
|
): Parcelable
|
@ -14,6 +14,7 @@ import com.alya.ecommerce_serang.data.api.response.auth.HasStoreResponse
|
|||||||
import com.alya.ecommerce_serang.data.api.response.auth.ListStoreTypeResponse
|
import com.alya.ecommerce_serang.data.api.response.auth.ListStoreTypeResponse
|
||||||
import com.alya.ecommerce_serang.data.api.response.auth.LoginResponse
|
import com.alya.ecommerce_serang.data.api.response.auth.LoginResponse
|
||||||
import com.alya.ecommerce_serang.data.api.response.auth.OtpResponse
|
import com.alya.ecommerce_serang.data.api.response.auth.OtpResponse
|
||||||
|
import com.alya.ecommerce_serang.data.api.response.auth.RegisterResponse
|
||||||
import com.alya.ecommerce_serang.data.api.response.auth.RegisterStoreResponse
|
import com.alya.ecommerce_serang.data.api.response.auth.RegisterStoreResponse
|
||||||
import com.alya.ecommerce_serang.data.api.response.auth.VerifRegisterResponse
|
import com.alya.ecommerce_serang.data.api.response.auth.VerifRegisterResponse
|
||||||
import com.alya.ecommerce_serang.data.api.response.customer.order.ListCityResponse
|
import com.alya.ecommerce_serang.data.api.response.customer.order.ListCityResponse
|
||||||
@ -59,12 +60,12 @@ class UserRepository(private val apiService: ApiService) {
|
|||||||
return if (response.isSuccessful) response.body() else null
|
return if (response.isSuccessful) response.body() else null
|
||||||
}
|
}
|
||||||
|
|
||||||
suspend fun registerUser(request: RegisterRequest): String {
|
suspend fun registerUser(request: RegisterRequest): RegisterResponse {
|
||||||
val response = apiService.register(request) // API call
|
val response = apiService.register(request) // API call
|
||||||
|
|
||||||
if (response.isSuccessful) {
|
if (response.isSuccessful) {
|
||||||
val responseBody = response.body() ?: throw Exception("Empty response body")
|
val responseBody = response.body() ?: throw Exception("Empty response body")
|
||||||
return responseBody.message // Get the message from RegisterResponse
|
return responseBody // Get the message from RegisterResponse
|
||||||
} else {
|
} else {
|
||||||
throw Exception("Registration failed: ${response.errorBody()?.string()}")
|
throw Exception("Registration failed: ${response.errorBody()?.string()}")
|
||||||
}
|
}
|
||||||
|
@ -1,49 +1,39 @@
|
|||||||
package com.alya.ecommerce_serang.ui.auth
|
package com.alya.ecommerce_serang.ui.auth
|
||||||
|
|
||||||
import android.app.DatePickerDialog
|
|
||||||
import android.content.Intent
|
import android.content.Intent
|
||||||
import android.os.Bundle
|
import android.os.Bundle
|
||||||
import android.util.Log
|
import android.util.Log
|
||||||
import android.widget.Toast
|
|
||||||
import androidx.activity.enableEdgeToEdge
|
import androidx.activity.enableEdgeToEdge
|
||||||
import androidx.activity.viewModels
|
import androidx.activity.viewModels
|
||||||
import androidx.appcompat.app.AppCompatActivity
|
import androidx.appcompat.app.AppCompatActivity
|
||||||
import androidx.core.view.ViewCompat
|
import androidx.core.view.ViewCompat
|
||||||
import androidx.core.view.WindowCompat
|
import androidx.core.view.WindowCompat
|
||||||
import androidx.core.view.WindowInsetsCompat
|
import androidx.core.view.WindowInsetsCompat
|
||||||
|
import com.alya.ecommerce_serang.R
|
||||||
import com.alya.ecommerce_serang.data.api.dto.RegisterRequest
|
import com.alya.ecommerce_serang.data.api.dto.RegisterRequest
|
||||||
import com.alya.ecommerce_serang.data.api.dto.VerifRegisReq
|
|
||||||
import com.alya.ecommerce_serang.data.api.retrofit.ApiConfig
|
import com.alya.ecommerce_serang.data.api.retrofit.ApiConfig
|
||||||
import com.alya.ecommerce_serang.data.repository.Result
|
import com.alya.ecommerce_serang.data.repository.OrderRepository
|
||||||
import com.alya.ecommerce_serang.data.repository.UserRepository
|
import com.alya.ecommerce_serang.data.repository.UserRepository
|
||||||
import com.alya.ecommerce_serang.databinding.ActivityRegisterBinding
|
import com.alya.ecommerce_serang.databinding.ActivityRegisterBinding
|
||||||
import com.alya.ecommerce_serang.ui.MainActivity
|
import com.alya.ecommerce_serang.ui.MainActivity
|
||||||
|
import com.alya.ecommerce_serang.ui.auth.fragments.RegisterStep1Fragment
|
||||||
|
import com.alya.ecommerce_serang.ui.auth.fragments.RegisterStep2Fragment
|
||||||
|
import com.alya.ecommerce_serang.ui.auth.fragments.RegisterStep3Fragment
|
||||||
import com.alya.ecommerce_serang.utils.BaseViewModelFactory
|
import com.alya.ecommerce_serang.utils.BaseViewModelFactory
|
||||||
import com.alya.ecommerce_serang.utils.SessionManager
|
import com.alya.ecommerce_serang.utils.SessionManager
|
||||||
import com.alya.ecommerce_serang.utils.viewmodel.RegisterViewModel
|
import com.alya.ecommerce_serang.utils.viewmodel.RegisterViewModel
|
||||||
import java.text.SimpleDateFormat
|
|
||||||
import java.util.Calendar
|
|
||||||
import java.util.Locale
|
|
||||||
|
|
||||||
class RegisterActivity : AppCompatActivity() {
|
class RegisterActivity : AppCompatActivity() {
|
||||||
private lateinit var binding: ActivityRegisterBinding
|
private lateinit var binding: ActivityRegisterBinding
|
||||||
private lateinit var sessionManager: SessionManager
|
private lateinit var sessionManager: SessionManager
|
||||||
|
|
||||||
private var isEmailValid = false
|
|
||||||
private var isPhoneValid = false
|
|
||||||
|
|
||||||
// Track which validation was last performed
|
|
||||||
private var lastCheckField = ""
|
|
||||||
|
|
||||||
// Counter for signup validation
|
|
||||||
private var signupValidationsComplete = 0
|
|
||||||
private var signupInProgress = false
|
|
||||||
|
|
||||||
private val registerViewModel: RegisterViewModel by viewModels{
|
private val registerViewModel: RegisterViewModel by viewModels{
|
||||||
BaseViewModelFactory {
|
BaseViewModelFactory {
|
||||||
val apiService = ApiConfig.getUnauthenticatedApiService()
|
val apiService = ApiConfig.getUnauthenticatedApiService()
|
||||||
|
val orderRepository = OrderRepository(apiService)
|
||||||
val userRepository = UserRepository(apiService)
|
val userRepository = UserRepository(apiService)
|
||||||
RegisterViewModel(userRepository)
|
RegisterViewModel(userRepository, orderRepository, this)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -88,264 +78,27 @@ class RegisterActivity : AppCompatActivity() {
|
|||||||
windowInsets
|
windowInsets
|
||||||
}
|
}
|
||||||
|
|
||||||
setupObservers()
|
if (savedInstanceState == null) {
|
||||||
|
supportFragmentManager.beginTransaction()
|
||||||
// Set up field validations
|
.replace(R.id.fragment_container, RegisterStep1Fragment.newInstance())
|
||||||
setupFieldValidations()
|
.commit()
|
||||||
|
|
||||||
binding.btnSignup.setOnClickListener {
|
|
||||||
handleSignUp()
|
|
||||||
}
|
|
||||||
|
|
||||||
binding.tvLoginAlt.setOnClickListener {
|
|
||||||
val intent = Intent(this, LoginActivity::class.java)
|
|
||||||
startActivity(intent)
|
|
||||||
}
|
|
||||||
|
|
||||||
binding.etBirthDate.setOnClickListener {
|
|
||||||
showDatePicker()
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun setupFieldValidations() {
|
// Function to navigate to the next fragment
|
||||||
// Validate email when focus changes
|
fun navigateToStep(step: Int, userData: RegisterRequest?) {
|
||||||
binding.etEmail.setOnFocusChangeListener { _, hasFocus ->
|
val fragment = when (step) {
|
||||||
if (!hasFocus) {
|
1 -> RegisterStep1Fragment.newInstance()
|
||||||
val email = binding.etEmail.text.toString()
|
2 -> RegisterStep2Fragment.newInstance(userData)
|
||||||
if (email.isNotEmpty()) {
|
3 -> RegisterStep3Fragment.newInstance()
|
||||||
validateEmail(email, false)
|
else -> null
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Validate phone when focus changes
|
fragment?.let {
|
||||||
binding.etNumberPhone.setOnFocusChangeListener { _, hasFocus ->
|
supportFragmentManager.beginTransaction()
|
||||||
if (!hasFocus) {
|
.replace(R.id.fragment_container, it)
|
||||||
val phone = binding.etNumberPhone.text.toString()
|
.addToBackStack(null)
|
||||||
if (phone.isNotEmpty()) {
|
.commit()
|
||||||
validatePhone(phone, false)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun validateEmail(email: String, isSignup: Boolean) {
|
|
||||||
lastCheckField = "email"
|
|
||||||
Log.d("RegisterActivity", "Validating email: $email (signup: $isSignup)")
|
|
||||||
|
|
||||||
val checkValueEmail = VerifRegisReq(
|
|
||||||
fieldRegis = "email",
|
|
||||||
valueRegis = email
|
|
||||||
)
|
|
||||||
registerViewModel.checkValueReg(checkValueEmail)
|
|
||||||
}
|
|
||||||
|
|
||||||
private fun validatePhone(phone: String, isSignup: Boolean) {
|
|
||||||
lastCheckField = "phone"
|
|
||||||
Log.d("RegisterActivity", "Validating phone: $phone (signup: $isSignup)")
|
|
||||||
|
|
||||||
val checkValuePhone = VerifRegisReq(
|
|
||||||
fieldRegis = "phone",
|
|
||||||
valueRegis = phone
|
|
||||||
)
|
|
||||||
registerViewModel.checkValueReg(checkValuePhone)
|
|
||||||
}
|
|
||||||
|
|
||||||
private fun setupObservers() {
|
|
||||||
|
|
||||||
registerViewModel.checkValue.observe(this) { result ->
|
|
||||||
when (result) {
|
|
||||||
is Result.Loading -> {
|
|
||||||
// Show loading if needed
|
|
||||||
}
|
|
||||||
is Result.Success -> {
|
|
||||||
val isValid = (result.data as? Boolean) ?: false
|
|
||||||
|
|
||||||
when (lastCheckField) {
|
|
||||||
"email" -> {
|
|
||||||
isEmailValid = isValid
|
|
||||||
if (!isValid) {
|
|
||||||
Toast.makeText(this, "Email is already registered", Toast.LENGTH_SHORT).show()
|
|
||||||
} else {
|
|
||||||
Log.d("RegisterActivity", "Email is valid")
|
|
||||||
}
|
|
||||||
}
|
|
||||||
"phone" -> {
|
|
||||||
isPhoneValid = isValid
|
|
||||||
if (!isValid) {
|
|
||||||
Toast.makeText(this, "Phone number is already registered", Toast.LENGTH_SHORT).show()
|
|
||||||
} else {
|
|
||||||
Log.d("RegisterActivity", "Phone is valid")
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Check if we're in signup process
|
|
||||||
if (signupInProgress) {
|
|
||||||
signupValidationsComplete++
|
|
||||||
|
|
||||||
// Check if both validations completed
|
|
||||||
if (signupValidationsComplete >= 2) {
|
|
||||||
signupInProgress = false
|
|
||||||
signupValidationsComplete = 0
|
|
||||||
|
|
||||||
// If both validations passed, request OTP
|
|
||||||
if (isEmailValid && isPhoneValid) {
|
|
||||||
requestOtp()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
is Result.Error -> {
|
|
||||||
val fieldType = if (lastCheckField == "email") "Email" else "Phone"
|
|
||||||
Toast.makeText(this, "$fieldType validation failed: ${result.exception.message}", Toast.LENGTH_SHORT).show()
|
|
||||||
|
|
||||||
// Mark validation as invalid
|
|
||||||
if (lastCheckField == "email") {
|
|
||||||
isEmailValid = false
|
|
||||||
} else if (lastCheckField == "phone") {
|
|
||||||
isPhoneValid = false
|
|
||||||
}
|
|
||||||
|
|
||||||
// Update signup validation counter if in signup process
|
|
||||||
if (signupInProgress) {
|
|
||||||
signupValidationsComplete++
|
|
||||||
|
|
||||||
// Check if both validations completed
|
|
||||||
if (signupValidationsComplete >= 2) {
|
|
||||||
signupInProgress = false
|
|
||||||
signupValidationsComplete = 0
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
else -> {
|
|
||||||
Log.e("RegisterActivity", "Unexpected result type: $result")
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
registerViewModel.otpState.observe(this) { result ->
|
|
||||||
when (result) {
|
|
||||||
is Result.Loading -> {
|
|
||||||
binding.progressBarOtp.visibility = android.view.View.VISIBLE
|
|
||||||
}
|
|
||||||
is Result.Success -> {
|
|
||||||
binding.progressBarOtp.visibility = android.view.View.GONE
|
|
||||||
Log.d("RegisterActivity", "OTP sent successfully. Showing OTP dialog.")
|
|
||||||
|
|
||||||
// Create user data before showing OTP dialog
|
|
||||||
val userData = createUserData()
|
|
||||||
|
|
||||||
// Show OTP dialog
|
|
||||||
val otpBottomSheet = OtpBottomSheetDialog(userData) { fullUserData ->
|
|
||||||
Log.d("RegisterActivity", "OTP entered successfully. Proceeding with registration.")
|
|
||||||
registerViewModel.registerUser(fullUserData)
|
|
||||||
}
|
|
||||||
otpBottomSheet.show(supportFragmentManager, "OtpBottomSheet")
|
|
||||||
}
|
|
||||||
is Result.Error -> {
|
|
||||||
binding.progressBarOtp.visibility = android.view.View.GONE
|
|
||||||
Toast.makeText(this, "OTP Request Failed: ${result.exception.message}", Toast.LENGTH_SHORT).show()
|
|
||||||
}
|
|
||||||
else -> {
|
|
||||||
Log.e("RegisterActivity", "Unexpected result type: $result")
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
registerViewModel.registerState.observe(this) { result ->
|
|
||||||
when (result) {
|
|
||||||
is Result.Loading -> {
|
|
||||||
// Show loading indicator for registration
|
|
||||||
binding.progressBarRegister.visibility = android.view.View.VISIBLE
|
|
||||||
}
|
|
||||||
is Result.Success -> {
|
|
||||||
// Hide loading indicator and show success message
|
|
||||||
binding.progressBarRegister.visibility = android.view.View.GONE
|
|
||||||
Toast.makeText(this, result.data, Toast.LENGTH_SHORT).show()
|
|
||||||
val intent = Intent(this, LoginActivity::class.java)
|
|
||||||
startActivity(intent)
|
|
||||||
// Navigate to another screen if needed
|
|
||||||
}
|
|
||||||
is com.alya.ecommerce_serang.data.repository.Result.Error -> {
|
|
||||||
// Hide loading indicator and show error message
|
|
||||||
binding.progressBarRegister.visibility = android.view.View.GONE
|
|
||||||
Toast.makeText(this, "Registration Failed: ${result.exception.message}", Toast.LENGTH_SHORT).show()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private fun handleSignUp() {
|
|
||||||
// Basic validation first
|
|
||||||
val email = binding.etEmail.text.toString()
|
|
||||||
val password = binding.etPassword.text.toString()
|
|
||||||
val confirmPassword = binding.etConfirmPassword.text.toString()
|
|
||||||
val phone = binding.etNumberPhone.text.toString()
|
|
||||||
val username = binding.etUsername.text.toString()
|
|
||||||
val name = binding.etFullname.text.toString()
|
|
||||||
val birthDate = binding.etBirthDate.text.toString()
|
|
||||||
|
|
||||||
// Check if fields are filled
|
|
||||||
if (email.isEmpty() || password.isEmpty() || confirmPassword.isEmpty() ||
|
|
||||||
phone.isEmpty() || username.isEmpty() || name.isEmpty() || birthDate.isEmpty()) {
|
|
||||||
Toast.makeText(this, "Please fill all required fields", Toast.LENGTH_SHORT).show()
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
// Check if passwords match
|
|
||||||
if (password != confirmPassword) {
|
|
||||||
Toast.makeText(this, "Passwords do not match", Toast.LENGTH_SHORT).show()
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
// If both validations are already done and successful, just request OTP
|
|
||||||
if (isEmailValid && isPhoneValid) {
|
|
||||||
requestOtp()
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
// Reset validation counters
|
|
||||||
signupInProgress = true
|
|
||||||
signupValidationsComplete = 0
|
|
||||||
|
|
||||||
// Start validations in parallel
|
|
||||||
validateEmail(email, true)
|
|
||||||
validatePhone(phone, true)
|
|
||||||
}
|
|
||||||
|
|
||||||
private fun requestOtp() {
|
|
||||||
val email = binding.etEmail.text.toString()
|
|
||||||
Log.d("RegisterActivity", "Requesting OTP for email: $email")
|
|
||||||
registerViewModel.requestOtp(email)
|
|
||||||
}
|
|
||||||
|
|
||||||
private fun createUserData(): RegisterRequest {
|
|
||||||
// Get all form values
|
|
||||||
val birthDate = binding.etBirthDate.text.toString()
|
|
||||||
val email = binding.etEmail.text.toString()
|
|
||||||
val password = binding.etPassword.text.toString()
|
|
||||||
val phone = binding.etNumberPhone.text.toString()
|
|
||||||
val username = binding.etUsername.text.toString()
|
|
||||||
val name = binding.etFullname.text.toString()
|
|
||||||
val image = null
|
|
||||||
|
|
||||||
// Create and return user data object
|
|
||||||
return RegisterRequest(name, email, password, username, phone, birthDate, image)
|
|
||||||
}
|
|
||||||
|
|
||||||
private fun showDatePicker() {
|
|
||||||
val calendar = Calendar.getInstance()
|
|
||||||
val year = calendar.get(Calendar.YEAR)
|
|
||||||
val month = calendar.get(Calendar.MONTH)
|
|
||||||
val day = calendar.get(Calendar.DAY_OF_MONTH)
|
|
||||||
|
|
||||||
DatePickerDialog(
|
|
||||||
this,
|
|
||||||
{ _, selectedYear, selectedMonth, selectedDay ->
|
|
||||||
calendar.set(selectedYear, selectedMonth, selectedDay)
|
|
||||||
val sdf = SimpleDateFormat("yyyy-MM-dd", Locale.getDefault())
|
|
||||||
binding.etBirthDate.setText(sdf.format(calendar.time))
|
|
||||||
},
|
|
||||||
year, month, day
|
|
||||||
).show()
|
|
||||||
}
|
|
||||||
}
|
}
|
@ -0,0 +1,268 @@
|
|||||||
|
package com.alya.ecommerce_serang.ui.auth.fragments
|
||||||
|
|
||||||
|
import android.app.DatePickerDialog
|
||||||
|
import android.content.Intent
|
||||||
|
import android.os.Bundle
|
||||||
|
import android.util.Log
|
||||||
|
import android.view.LayoutInflater
|
||||||
|
import android.view.View
|
||||||
|
import android.view.ViewGroup
|
||||||
|
import android.widget.TextView
|
||||||
|
import android.widget.Toast
|
||||||
|
import androidx.fragment.app.Fragment
|
||||||
|
import androidx.fragment.app.activityViewModels
|
||||||
|
import com.alya.ecommerce_serang.R
|
||||||
|
import com.alya.ecommerce_serang.data.api.dto.RegisterRequest
|
||||||
|
import com.alya.ecommerce_serang.data.api.dto.VerifRegisReq
|
||||||
|
import com.alya.ecommerce_serang.data.api.retrofit.ApiConfig
|
||||||
|
import com.alya.ecommerce_serang.data.repository.OrderRepository
|
||||||
|
import com.alya.ecommerce_serang.data.repository.Result
|
||||||
|
import com.alya.ecommerce_serang.data.repository.UserRepository
|
||||||
|
import com.alya.ecommerce_serang.databinding.FragmentRegisterStep1Binding
|
||||||
|
import com.alya.ecommerce_serang.ui.auth.LoginActivity
|
||||||
|
import com.alya.ecommerce_serang.ui.auth.RegisterActivity
|
||||||
|
import com.alya.ecommerce_serang.utils.BaseViewModelFactory
|
||||||
|
import com.alya.ecommerce_serang.utils.viewmodel.RegisterViewModel
|
||||||
|
import com.google.android.material.progressindicator.LinearProgressIndicator
|
||||||
|
import java.text.SimpleDateFormat
|
||||||
|
import java.util.Calendar
|
||||||
|
import java.util.Locale
|
||||||
|
|
||||||
|
class RegisterStep1Fragment : Fragment() {
|
||||||
|
private var _binding: FragmentRegisterStep1Binding? = null
|
||||||
|
private val binding get() = _binding!!
|
||||||
|
|
||||||
|
private val registerViewModel: RegisterViewModel by activityViewModels {
|
||||||
|
BaseViewModelFactory {
|
||||||
|
val apiService = ApiConfig.getUnauthenticatedApiService()
|
||||||
|
val orderRepository = OrderRepository(apiService)
|
||||||
|
val userRepository = UserRepository(apiService)
|
||||||
|
RegisterViewModel(userRepository, orderRepository, requireContext())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
private var isEmailValid = false
|
||||||
|
private var isPhoneValid = false
|
||||||
|
|
||||||
|
companion object {
|
||||||
|
private const val TAG = "RegisterStep1Fragment"
|
||||||
|
|
||||||
|
fun newInstance() = RegisterStep1Fragment()
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun onCreateView(
|
||||||
|
inflater: LayoutInflater,
|
||||||
|
container: ViewGroup?,
|
||||||
|
savedInstanceState: Bundle?
|
||||||
|
): View {
|
||||||
|
_binding = FragmentRegisterStep1Binding.inflate(inflater, container, false)
|
||||||
|
return binding.root
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
|
||||||
|
super.onViewCreated(view, savedInstanceState)
|
||||||
|
|
||||||
|
// Set step progress and description
|
||||||
|
(activity as? RegisterActivity)?.let {
|
||||||
|
it.findViewById<LinearProgressIndicator>(R.id.registration_progress)?.progress = 33
|
||||||
|
it.findViewById<TextView>(R.id.tv_step_title)?.text = "Step 1: Account & Personal Info"
|
||||||
|
it.findViewById<TextView>(R.id.tv_step_description)?.text =
|
||||||
|
"Fill in your account and personal details to create your profile."
|
||||||
|
}
|
||||||
|
|
||||||
|
setupFieldValidations()
|
||||||
|
setupObservers()
|
||||||
|
setupDatePicker()
|
||||||
|
|
||||||
|
binding.btnNext.setOnClickListener {
|
||||||
|
validateAndProceed()
|
||||||
|
}
|
||||||
|
|
||||||
|
binding.tvLoginAlt.setOnClickListener {
|
||||||
|
startActivity(Intent(requireContext(), LoginActivity::class.java))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun setupDatePicker() {
|
||||||
|
binding.etBirthDate.setOnClickListener {
|
||||||
|
showDatePicker()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun showDatePicker() {
|
||||||
|
val calendar = Calendar.getInstance()
|
||||||
|
val year = calendar.get(Calendar.YEAR)
|
||||||
|
val month = calendar.get(Calendar.MONTH)
|
||||||
|
val day = calendar.get(Calendar.DAY_OF_MONTH)
|
||||||
|
|
||||||
|
DatePickerDialog(
|
||||||
|
requireContext(),
|
||||||
|
{ _, selectedYear, selectedMonth, selectedDay ->
|
||||||
|
calendar.set(selectedYear, selectedMonth, selectedDay)
|
||||||
|
val sdf = SimpleDateFormat("yyyy-MM-dd", Locale.getDefault())
|
||||||
|
binding.etBirthDate.setText(sdf.format(calendar.time))
|
||||||
|
},
|
||||||
|
year, month, day
|
||||||
|
).show()
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun setupFieldValidations() {
|
||||||
|
// Validate email when focus changes
|
||||||
|
binding.etEmail.setOnFocusChangeListener { _, hasFocus ->
|
||||||
|
if (!hasFocus) {
|
||||||
|
val email = binding.etEmail.text.toString()
|
||||||
|
if (email.isNotEmpty()) {
|
||||||
|
validateEmail(email)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Validate phone when focus changes
|
||||||
|
binding.etNumberPhone.setOnFocusChangeListener { _, hasFocus ->
|
||||||
|
if (!hasFocus) {
|
||||||
|
val phone = binding.etNumberPhone.text.toString()
|
||||||
|
if (phone.isNotEmpty()) {
|
||||||
|
validatePhone(phone)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun validateEmail(email: String) {
|
||||||
|
val checkValueEmail = VerifRegisReq(
|
||||||
|
fieldRegis = "email",
|
||||||
|
valueRegis = email
|
||||||
|
)
|
||||||
|
registerViewModel.checkValueReg(checkValueEmail)
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun validatePhone(phone: String) {
|
||||||
|
val checkValuePhone = VerifRegisReq(
|
||||||
|
fieldRegis = "phone",
|
||||||
|
valueRegis = phone
|
||||||
|
)
|
||||||
|
registerViewModel.checkValueReg(checkValuePhone)
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun setupObservers() {
|
||||||
|
registerViewModel.checkValue.observe(viewLifecycleOwner) { result ->
|
||||||
|
when (result) {
|
||||||
|
is com.alya.ecommerce_serang.data.repository.Result.Loading -> {
|
||||||
|
// Show loading if needed
|
||||||
|
}
|
||||||
|
is com.alya.ecommerce_serang.data.repository.Result.Success -> {
|
||||||
|
val isValid = (result.data as? Boolean) ?: false
|
||||||
|
when (val fieldType = registerViewModel.lastCheckedField) {
|
||||||
|
"email" -> {
|
||||||
|
isEmailValid = isValid
|
||||||
|
if (!isValid) {
|
||||||
|
Toast.makeText(requireContext(), "Email is already registered", Toast.LENGTH_SHORT).show()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
"phone" -> {
|
||||||
|
isPhoneValid = isValid
|
||||||
|
if (!isValid) {
|
||||||
|
Toast.makeText(requireContext(), "Phone number is already registered", Toast.LENGTH_SHORT).show()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
is com.alya.ecommerce_serang.data.repository.Result.Error -> {
|
||||||
|
Toast.makeText(requireContext(), "Validation failed: ${result.exception.message}", Toast.LENGTH_SHORT).show()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
registerViewModel.otpState.observe(viewLifecycleOwner) { result ->
|
||||||
|
when (result) {
|
||||||
|
is com.alya.ecommerce_serang.data.repository.Result.Loading -> {
|
||||||
|
binding.progressBar.visibility = View.VISIBLE
|
||||||
|
binding.btnNext.isEnabled = false
|
||||||
|
}
|
||||||
|
is com.alya.ecommerce_serang.data.repository.Result.Success -> {
|
||||||
|
binding.progressBar.visibility = View.GONE
|
||||||
|
binding.btnNext.isEnabled = true
|
||||||
|
|
||||||
|
// Create user data with both account and personal info
|
||||||
|
val userData = RegisterRequest(
|
||||||
|
name = binding.etFullname.text.toString(),
|
||||||
|
email = binding.etEmail.text.toString(),
|
||||||
|
password = binding.etPassword.text.toString(),
|
||||||
|
username = binding.etUsername.text.toString(),
|
||||||
|
phone = binding.etNumberPhone.text.toString(),
|
||||||
|
birthDate = binding.etBirthDate.text.toString(),
|
||||||
|
otp = "" // Will be filled in step 2
|
||||||
|
)
|
||||||
|
|
||||||
|
registerViewModel.updateUserData(userData)
|
||||||
|
registerViewModel.setStep(2)
|
||||||
|
(activity as? RegisterActivity)?.navigateToStep(2, userData)
|
||||||
|
}
|
||||||
|
is Result.Error -> {
|
||||||
|
binding.progressBar.visibility = View.GONE
|
||||||
|
binding.btnNext.isEnabled = true
|
||||||
|
Toast.makeText(requireContext(), "OTP Request Failed: ${result.exception.message}", Toast.LENGTH_SHORT).show()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun validateAndProceed() {
|
||||||
|
// Validate account information
|
||||||
|
val email = binding.etEmail.text.toString()
|
||||||
|
val password = binding.etPassword.text.toString()
|
||||||
|
val confirmPassword = binding.etConfirmPassword.text.toString()
|
||||||
|
val phone = binding.etNumberPhone.text.toString()
|
||||||
|
val username = binding.etUsername.text.toString()
|
||||||
|
|
||||||
|
// Validate personal information
|
||||||
|
val fullName = binding.etFullname.text.toString()
|
||||||
|
val birthDate = binding.etBirthDate.text.toString()
|
||||||
|
// val gender = binding.etGender.text.toString()
|
||||||
|
|
||||||
|
// Check if all fields are filled
|
||||||
|
if (email.isEmpty() || password.isEmpty() || confirmPassword.isEmpty() || phone.isEmpty() ||
|
||||||
|
username.isEmpty() || fullName.isEmpty() || birthDate.isEmpty()) {
|
||||||
|
Toast.makeText(requireContext(), "Please fill all required fields", Toast.LENGTH_SHORT).show()
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// Check if passwords match
|
||||||
|
if (password != confirmPassword) {
|
||||||
|
Toast.makeText(requireContext(), "Passwords do not match", Toast.LENGTH_SHORT).show()
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// If both validations are already done and successful, request OTP
|
||||||
|
if (isEmailValid && isPhoneValid) {
|
||||||
|
requestOtp(email)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// Validate email and phone
|
||||||
|
validateEmail(email)
|
||||||
|
validatePhone(phone)
|
||||||
|
|
||||||
|
// Only proceed if both are valid
|
||||||
|
if (isEmailValid && isPhoneValid) {
|
||||||
|
requestOtp(email)
|
||||||
|
} else {
|
||||||
|
Toast.makeText(requireContext(), "Please fix validation errors before proceeding", Toast.LENGTH_SHORT).show()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun requestOtp(email: String) {
|
||||||
|
|
||||||
|
registerViewModel.requestOtp(email)
|
||||||
|
|
||||||
|
registerViewModel.message.observe(viewLifecycleOwner) { message ->
|
||||||
|
Log.d(TAG, "Message from server: $message")
|
||||||
|
// You can use the message here if needed, e.g., for showing in a specific UI element
|
||||||
|
// or for storing for later use
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun onDestroyView() {
|
||||||
|
super.onDestroyView()
|
||||||
|
_binding = null
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,291 @@
|
|||||||
|
package com.alya.ecommerce_serang.ui.auth.fragments
|
||||||
|
|
||||||
|
import android.os.Build
|
||||||
|
import android.os.Bundle
|
||||||
|
import android.os.CountDownTimer
|
||||||
|
import android.util.Log
|
||||||
|
import android.view.LayoutInflater
|
||||||
|
import android.view.View
|
||||||
|
import android.view.ViewGroup
|
||||||
|
import android.widget.TextView
|
||||||
|
import android.widget.Toast
|
||||||
|
import androidx.core.content.ContextCompat
|
||||||
|
import androidx.fragment.app.Fragment
|
||||||
|
import androidx.fragment.app.activityViewModels
|
||||||
|
import com.alya.ecommerce_serang.R
|
||||||
|
import com.alya.ecommerce_serang.data.api.dto.RegisterRequest
|
||||||
|
import com.alya.ecommerce_serang.data.api.retrofit.ApiConfig
|
||||||
|
import com.alya.ecommerce_serang.data.repository.OrderRepository
|
||||||
|
import com.alya.ecommerce_serang.data.repository.Result
|
||||||
|
import com.alya.ecommerce_serang.data.repository.UserRepository
|
||||||
|
import com.alya.ecommerce_serang.databinding.FragmentRegisterStep2Binding
|
||||||
|
import com.alya.ecommerce_serang.ui.auth.RegisterActivity
|
||||||
|
import com.alya.ecommerce_serang.utils.BaseViewModelFactory
|
||||||
|
import com.alya.ecommerce_serang.utils.SessionManager
|
||||||
|
import com.alya.ecommerce_serang.utils.viewmodel.RegisterViewModel
|
||||||
|
import com.google.android.material.progressindicator.LinearProgressIndicator
|
||||||
|
|
||||||
|
class RegisterStep2Fragment : Fragment() {
|
||||||
|
private var _binding: FragmentRegisterStep2Binding? = null
|
||||||
|
private val binding get() = _binding!!
|
||||||
|
private lateinit var sessionManager: SessionManager
|
||||||
|
|
||||||
|
// In RegisterStep2Fragment AND RegisterStep3Fragment:
|
||||||
|
private val registerViewModel: RegisterViewModel by activityViewModels {
|
||||||
|
BaseViewModelFactory {
|
||||||
|
val apiService = ApiConfig.getUnauthenticatedApiService()
|
||||||
|
val orderRepository = OrderRepository(apiService)
|
||||||
|
val userRepository = UserRepository(apiService)
|
||||||
|
RegisterViewModel(userRepository, orderRepository, requireContext())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
private var countDownTimer: CountDownTimer? = null
|
||||||
|
private var timeRemaining = 30 // 30 seconds cooldown for resend
|
||||||
|
|
||||||
|
companion object {
|
||||||
|
|
||||||
|
private const val TAG = "RegisterStep2Fragment"
|
||||||
|
fun newInstance(userData: RegisterRequest?) = RegisterStep2Fragment().apply {
|
||||||
|
arguments = Bundle().apply {
|
||||||
|
putParcelable("userData", userData)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun onCreateView(
|
||||||
|
inflater: LayoutInflater,
|
||||||
|
container: ViewGroup?,
|
||||||
|
savedInstanceState: Bundle?
|
||||||
|
): View {
|
||||||
|
_binding = FragmentRegisterStep2Binding.inflate(inflater, container, false)
|
||||||
|
return binding.root
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
|
||||||
|
super.onViewCreated(view, savedInstanceState)
|
||||||
|
|
||||||
|
sessionManager = SessionManager(requireContext())
|
||||||
|
Log.d(TAG, "SessionManager initialized, token: ${sessionManager.getToken()}")
|
||||||
|
|
||||||
|
// Set step progress and description
|
||||||
|
(activity as? RegisterActivity)?.let {
|
||||||
|
it.findViewById<LinearProgressIndicator>(R.id.registration_progress)?.progress = 66
|
||||||
|
it.findViewById<TextView>(R.id.tv_step_title)?.text = "Step 2: Verify Your Email"
|
||||||
|
it.findViewById<TextView>(R.id.tv_step_description)?.text =
|
||||||
|
"Enter the verification code sent to your email to continue."
|
||||||
|
Log.d(TAG, "Step indicators updated to Step 2")
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
// Get the user data from arguments
|
||||||
|
val userData = if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU) {
|
||||||
|
arguments?.getParcelable("userData", RegisterRequest::class.java)
|
||||||
|
} else {
|
||||||
|
@Suppress("DEPRECATION")
|
||||||
|
arguments?.getParcelable("userData") as? RegisterRequest
|
||||||
|
}
|
||||||
|
Log.d(TAG, "User data retrieved from arguments: ${userData?.email}, ${userData?.name}")
|
||||||
|
|
||||||
|
// Update the email sent message
|
||||||
|
userData?.let {
|
||||||
|
binding.tvEmailSent.text = "We've sent a verification code to ${it.email}"
|
||||||
|
}
|
||||||
|
|
||||||
|
// Start the resend cooldown timer
|
||||||
|
startResendCooldown()
|
||||||
|
Log.d(TAG, "Resend cooldown timer started")
|
||||||
|
|
||||||
|
// Set up button listeners
|
||||||
|
binding.btnVerify.setOnClickListener {
|
||||||
|
verifyOtp(userData)
|
||||||
|
}
|
||||||
|
|
||||||
|
binding.tvResendOtp.setOnClickListener {
|
||||||
|
if (timeRemaining <= 0) {
|
||||||
|
Log.d(TAG, "Resend OTP clicked, remaining time: $timeRemaining")
|
||||||
|
resendOtp(userData?.email)
|
||||||
|
} else {
|
||||||
|
Log.d(TAG, "Resend OTP clicked but cooldown active, remaining time: $timeRemaining")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
observeRegistrationState()
|
||||||
|
observeLoginState()
|
||||||
|
Log.d(TAG, "Registration and login state observers set up")
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun verifyOtp(userData: RegisterRequest?) {
|
||||||
|
val otp = binding.etOtp.text.toString()
|
||||||
|
Log.d(TAG, "verifyOtp called with OTP: $otp")
|
||||||
|
|
||||||
|
if (otp.isEmpty()) {
|
||||||
|
Toast.makeText(requireContext(), "Please enter the verification code", Toast.LENGTH_SHORT).show()
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// Update the user data with the OTP
|
||||||
|
userData?.let {
|
||||||
|
val updatedUserData = it.copy(otp = otp)
|
||||||
|
Log.d(TAG, "Updating user data with OTP: $otp")
|
||||||
|
registerViewModel.updateUserData(updatedUserData)
|
||||||
|
|
||||||
|
// For demo purposes, we're just proceeding to Step 3
|
||||||
|
// In a real app, you would verify the OTP with the server first
|
||||||
|
// registerViewModel.setStep(3)
|
||||||
|
// (activity as? RegisterActivity)?.navigateToStep(3, updatedUserData)
|
||||||
|
|
||||||
|
registerViewModel.registerUser(updatedUserData)
|
||||||
|
} ?: Log.e(TAG, "userData is null, cannot proceed with verification")
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun resendOtp(email: String?) {
|
||||||
|
Log.d(TAG, "resendOtp called for email: $email")
|
||||||
|
email?.let {
|
||||||
|
binding.progressBar.visibility = View.VISIBLE
|
||||||
|
Log.d(TAG, "Requesting OTP for: $it")
|
||||||
|
registerViewModel.requestOtp(it)
|
||||||
|
|
||||||
|
// Observe the OTP state
|
||||||
|
registerViewModel.otpState.observe(viewLifecycleOwner) { result ->
|
||||||
|
when (result) {
|
||||||
|
is com.alya.ecommerce_serang.data.repository.Result.Loading -> {
|
||||||
|
binding.progressBar.visibility = View.VISIBLE
|
||||||
|
}
|
||||||
|
is com.alya.ecommerce_serang.data.repository.Result.Success -> {
|
||||||
|
binding.progressBar.visibility = View.GONE
|
||||||
|
Toast.makeText(requireContext(), "Verification code resent", Toast.LENGTH_SHORT).show()
|
||||||
|
startResendCooldown()
|
||||||
|
}
|
||||||
|
is Result.Error -> {
|
||||||
|
Log.e(TAG, "OTP request: Error - ${result.exception.message}")
|
||||||
|
binding.progressBar.visibility = View.GONE
|
||||||
|
Toast.makeText(requireContext(), "Failed to resend code: ${result.exception.message}", Toast.LENGTH_SHORT).show()
|
||||||
|
}
|
||||||
|
else -> {
|
||||||
|
Log.d(TAG, "OTP request: Unknown state")
|
||||||
|
binding.progressBar.visibility = View.GONE
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} ?: Log.e(TAG, "Cannot resend OTP: email is null")
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun startResendCooldown() {
|
||||||
|
Log.d(TAG, "startResendCooldown called")
|
||||||
|
timeRemaining = 30
|
||||||
|
binding.tvResendOtp.isEnabled = false
|
||||||
|
binding.tvResendOtp.setTextColor(ContextCompat.getColor(requireContext(), R.color.soft_gray))
|
||||||
|
|
||||||
|
countDownTimer?.cancel()
|
||||||
|
countDownTimer = object : CountDownTimer(30000, 1000) {
|
||||||
|
override fun onTick(millisUntilFinished: Long) {
|
||||||
|
timeRemaining = (millisUntilFinished / 1000).toInt()
|
||||||
|
binding.tvTimer.text = "Resend available in 00:${String.format("%02d", timeRemaining)}"
|
||||||
|
if (timeRemaining % 5 == 0) {
|
||||||
|
Log.d(TAG, "Cooldown remaining: $timeRemaining seconds")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun onFinish() {
|
||||||
|
Log.d(TAG, "Cooldown finished, enabling resend button")
|
||||||
|
binding.tvTimer.text = "You can now resend the code"
|
||||||
|
binding.tvResendOtp.isEnabled = true
|
||||||
|
binding.tvResendOtp.setTextColor(ContextCompat.getColor(requireContext(), R.color.blue1))
|
||||||
|
timeRemaining = 0
|
||||||
|
}
|
||||||
|
}.start()
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun observeRegistrationState() {
|
||||||
|
registerViewModel.message.observe(viewLifecycleOwner) { message ->
|
||||||
|
Log.d(TAG, "Message from server: $message")
|
||||||
|
// You can use the message here if needed, e.g., for showing in a specific UI element
|
||||||
|
// or for storing for later use
|
||||||
|
}
|
||||||
|
registerViewModel.registerState.observe(viewLifecycleOwner) { result ->
|
||||||
|
when (result) {
|
||||||
|
is Result.Loading -> {
|
||||||
|
binding.progressBar.visibility = View.VISIBLE
|
||||||
|
binding.btnVerify.isEnabled = false
|
||||||
|
}
|
||||||
|
is Result.Success -> {
|
||||||
|
Log.d(TAG, "Registration: Success - ${result.data}")
|
||||||
|
// Don't hide progress bar or re-enable button yet
|
||||||
|
// We'll wait for login to complete
|
||||||
|
|
||||||
|
// Don't show success toast yet - wait until address is added
|
||||||
|
Log.d("RegisterStep2Fragment", "Registration successful, waiting for login")
|
||||||
|
}
|
||||||
|
is Result.Error -> {
|
||||||
|
Log.e(TAG, "Registration: Error - ${result.exception.message}", result.exception)
|
||||||
|
binding.progressBar.visibility = View.GONE
|
||||||
|
binding.btnVerify.isEnabled = true
|
||||||
|
|
||||||
|
// Show error message
|
||||||
|
Toast.makeText(requireContext(), "Registration Failed: ${result.exception.message}", Toast.LENGTH_SHORT).show()
|
||||||
|
}
|
||||||
|
else -> {
|
||||||
|
Log.d(TAG, "Registration: Unknown state")
|
||||||
|
binding.progressBar.visibility = View.GONE
|
||||||
|
binding.btnVerify.isEnabled = true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun observeLoginState() {
|
||||||
|
registerViewModel.loginState.observe(viewLifecycleOwner) { result ->
|
||||||
|
when (result) {
|
||||||
|
is Result.Loading -> {
|
||||||
|
// Keep showing progress
|
||||||
|
binding.progressBar.visibility = View.VISIBLE
|
||||||
|
binding.btnVerify.isEnabled = false
|
||||||
|
}
|
||||||
|
is Result.Success -> {
|
||||||
|
Log.d(TAG, "Login: Success - token received")
|
||||||
|
binding.progressBar.visibility = View.GONE
|
||||||
|
binding.btnVerify.isEnabled = true
|
||||||
|
|
||||||
|
// Save the token in fragment
|
||||||
|
val accessToken = result.data.accessToken
|
||||||
|
sessionManager.saveToken(accessToken)
|
||||||
|
Log.d(TAG, "Token saved to SessionManager: $accessToken")
|
||||||
|
|
||||||
|
// Also save user ID if available in the login response
|
||||||
|
// result.data.?.let { userId ->
|
||||||
|
// sessionManager.saveUserId(userId)
|
||||||
|
// }
|
||||||
|
|
||||||
|
Log.d(TAG, "Login successful, token saved: $accessToken")
|
||||||
|
|
||||||
|
// Proceed to Step 3
|
||||||
|
Log.d(TAG, "Proceeding to Step 3 after successful login")
|
||||||
|
(activity as? RegisterActivity)?.navigateToStep(3, null )
|
||||||
|
}
|
||||||
|
is Result.Error -> {
|
||||||
|
Log.e(TAG, "Login: Error - ${result.exception.message}", result.exception)
|
||||||
|
binding.progressBar.visibility = View.GONE
|
||||||
|
binding.btnVerify.isEnabled = true
|
||||||
|
|
||||||
|
// Show error message but continue to Step 3 anyway
|
||||||
|
Log.e(TAG, "Login failed but proceeding to Step 3", result.exception)
|
||||||
|
Toast.makeText(requireContext(), "Note: Auto-login failed, but registration was successful", Toast.LENGTH_SHORT).show()
|
||||||
|
|
||||||
|
// Proceed to Step 3
|
||||||
|
(activity as? RegisterActivity)?.navigateToStep(3, null)
|
||||||
|
}
|
||||||
|
else -> {
|
||||||
|
Log.d(TAG, "Login: Unknown state")
|
||||||
|
binding.progressBar.visibility = View.GONE
|
||||||
|
binding.btnVerify.isEnabled = true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun onDestroyView() {
|
||||||
|
super.onDestroyView()
|
||||||
|
countDownTimer?.cancel()
|
||||||
|
_binding = null
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,360 @@
|
|||||||
|
package com.alya.ecommerce_serang.ui.auth.fragments
|
||||||
|
|
||||||
|
import android.content.Intent
|
||||||
|
import android.os.Bundle
|
||||||
|
import android.util.Log
|
||||||
|
import android.view.LayoutInflater
|
||||||
|
import android.view.View
|
||||||
|
import android.view.ViewGroup
|
||||||
|
import android.widget.TextView
|
||||||
|
import android.widget.Toast
|
||||||
|
import androidx.fragment.app.Fragment
|
||||||
|
import androidx.fragment.app.activityViewModels
|
||||||
|
import com.alya.ecommerce_serang.R
|
||||||
|
import com.alya.ecommerce_serang.data.api.dto.CreateAddressRequest
|
||||||
|
import com.alya.ecommerce_serang.data.api.retrofit.ApiConfig
|
||||||
|
import com.alya.ecommerce_serang.data.repository.OrderRepository
|
||||||
|
import com.alya.ecommerce_serang.data.repository.UserRepository
|
||||||
|
import com.alya.ecommerce_serang.databinding.FragmentRegisterStep3Binding
|
||||||
|
import com.alya.ecommerce_serang.ui.auth.LoginActivity
|
||||||
|
import com.alya.ecommerce_serang.ui.auth.RegisterActivity
|
||||||
|
import com.alya.ecommerce_serang.ui.order.address.CityAdapter
|
||||||
|
import com.alya.ecommerce_serang.ui.order.address.ProvinceAdapter
|
||||||
|
import com.alya.ecommerce_serang.ui.order.address.ViewState
|
||||||
|
import com.alya.ecommerce_serang.utils.BaseViewModelFactory
|
||||||
|
import com.alya.ecommerce_serang.utils.SessionManager
|
||||||
|
import com.alya.ecommerce_serang.utils.viewmodel.RegisterViewModel
|
||||||
|
import com.google.android.material.progressindicator.LinearProgressIndicator
|
||||||
|
|
||||||
|
class RegisterStep3Fragment : Fragment() {
|
||||||
|
private var _binding: FragmentRegisterStep3Binding? = null
|
||||||
|
private val binding get() = _binding!!
|
||||||
|
private lateinit var sessionManager: SessionManager
|
||||||
|
|
||||||
|
private val defaultLatitude = -6.200000
|
||||||
|
private val defaultLongitude = 106.816666
|
||||||
|
|
||||||
|
// In RegisterStep2Fragment AND RegisterStep3Fragment:
|
||||||
|
private val registerViewModel: RegisterViewModel by activityViewModels {
|
||||||
|
BaseViewModelFactory {
|
||||||
|
val apiService = ApiConfig.getUnauthenticatedApiService()
|
||||||
|
val orderRepository = OrderRepository(apiService)
|
||||||
|
val userRepository = UserRepository(apiService)
|
||||||
|
RegisterViewModel(userRepository, orderRepository, requireContext())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// For province and city selection
|
||||||
|
private val provinceAdapter by lazy { ProvinceAdapter(requireContext()) }
|
||||||
|
private val cityAdapter by lazy { CityAdapter(requireContext()) }
|
||||||
|
|
||||||
|
companion object {
|
||||||
|
private const val TAG = "RegisterStep3Fragment"
|
||||||
|
|
||||||
|
fun newInstance() = RegisterStep3Fragment()
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun onCreateView(
|
||||||
|
inflater: LayoutInflater,
|
||||||
|
container: ViewGroup?,
|
||||||
|
savedInstanceState: Bundle?
|
||||||
|
): View {
|
||||||
|
_binding = FragmentRegisterStep3Binding.inflate(inflater, container, false)
|
||||||
|
return binding.root
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
|
||||||
|
super.onViewCreated(view, savedInstanceState)
|
||||||
|
|
||||||
|
sessionManager = SessionManager(requireContext())
|
||||||
|
Log.d(TAG, "SessionManager initialized, token: ${sessionManager.getToken()}")
|
||||||
|
|
||||||
|
// Set step progress and description
|
||||||
|
(activity as? RegisterActivity)?.let {
|
||||||
|
it.findViewById<LinearProgressIndicator>(R.id.registration_progress)?.progress = 33
|
||||||
|
it.findViewById<TextView>(R.id.tv_step_title)?.text = "Step 1: Account & Personal Info"
|
||||||
|
it.findViewById<TextView>(R.id.tv_step_description)?.text =
|
||||||
|
"Fill in your account and personal details to create your profile."
|
||||||
|
Log.d(TAG, "Step indicators updated to Step 1")
|
||||||
|
}
|
||||||
|
|
||||||
|
// Get registered user data
|
||||||
|
val user = registerViewModel.registeredUser.value
|
||||||
|
Log.d(TAG, "Retrieved user data: ${user?.name}, ID: ${user?.id}")
|
||||||
|
|
||||||
|
// Auto-fill recipient name and phone if available
|
||||||
|
user?.let {
|
||||||
|
binding.etNamaPenerima.setText(it.name)
|
||||||
|
binding.etNomorHp.setText(it.phone)
|
||||||
|
Log.d(TAG, "Auto-filled name: ${it.name}, phone: ${it.phone}")
|
||||||
|
}
|
||||||
|
|
||||||
|
// Set up province and city dropdowns
|
||||||
|
setupAutoComplete()
|
||||||
|
|
||||||
|
// Set up button listeners
|
||||||
|
binding.btnPrevious.setOnClickListener {
|
||||||
|
// Go back to the previous step
|
||||||
|
parentFragmentManager.popBackStack()
|
||||||
|
}
|
||||||
|
|
||||||
|
binding.btnRegister.setOnClickListener {
|
||||||
|
submitAddress()
|
||||||
|
}
|
||||||
|
|
||||||
|
// If user skips address entry
|
||||||
|
// binding.btnSkip.setOnClickListener {
|
||||||
|
// showRegistrationSuccess()
|
||||||
|
// }
|
||||||
|
|
||||||
|
// Observe address submission state
|
||||||
|
observeAddressSubmissionState()
|
||||||
|
|
||||||
|
// Load provinces
|
||||||
|
Log.d(TAG, "Requesting provinces data")
|
||||||
|
registerViewModel.getProvinces()
|
||||||
|
setupProvinceObserver()
|
||||||
|
setupCityObserver()
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun setupAutoComplete() {
|
||||||
|
// Same implementation as before
|
||||||
|
binding.autoCompleteProvinsi.setAdapter(provinceAdapter)
|
||||||
|
binding.autoCompleteKabupaten.setAdapter(cityAdapter)
|
||||||
|
|
||||||
|
binding.autoCompleteProvinsi.setOnClickListener {
|
||||||
|
binding.autoCompleteProvinsi.showDropDown()
|
||||||
|
}
|
||||||
|
|
||||||
|
binding.autoCompleteKabupaten.setOnClickListener {
|
||||||
|
if (cityAdapter.count > 0) {
|
||||||
|
Log.d(TAG, "City dropdown clicked, showing ${cityAdapter.count} cities")
|
||||||
|
binding.autoCompleteKabupaten.showDropDown()
|
||||||
|
} else {
|
||||||
|
Toast.makeText(requireContext(), "Pilih provinsi terlebih dahulu", Toast.LENGTH_SHORT).show()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
binding.autoCompleteProvinsi.setOnItemClickListener { _, _, position, _ ->
|
||||||
|
val provinceId = provinceAdapter.getProvinceId(position)
|
||||||
|
Log.d(TAG, "Province selected at position $position, ID: $provinceId")
|
||||||
|
|
||||||
|
provinceId?.let { id ->
|
||||||
|
registerViewModel.selectedProvinceId = id
|
||||||
|
Log.d(TAG, "Requesting cities for province ID: $id")
|
||||||
|
registerViewModel.getCities(id)
|
||||||
|
binding.autoCompleteKabupaten.text.clear()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
binding.autoCompleteKabupaten.setOnItemClickListener { _, _, position, _ ->
|
||||||
|
val cityId = cityAdapter.getCityId(position)
|
||||||
|
Log.d(TAG, "City selected at position $position, ID: $cityId")
|
||||||
|
|
||||||
|
cityId?.let { id ->
|
||||||
|
Log.d(TAG, "Selected city ID set to: $id")
|
||||||
|
registerViewModel.selectedCityId = id
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun setupProvinceObserver() {
|
||||||
|
// Same implementation as before
|
||||||
|
registerViewModel.provincesState.observe(viewLifecycleOwner) { state ->
|
||||||
|
when (state) {
|
||||||
|
is ViewState.Loading -> {
|
||||||
|
binding.progressBarProvinsi.visibility = View.VISIBLE
|
||||||
|
}
|
||||||
|
is ViewState.Success -> {
|
||||||
|
Log.d(TAG, "Provinces: Success - received ${state.data.size} provinces")
|
||||||
|
binding.progressBarProvinsi.visibility = View.GONE
|
||||||
|
if (state.data.isNotEmpty()) {
|
||||||
|
provinceAdapter.updateData(state.data)
|
||||||
|
} else {
|
||||||
|
showError("No provinces available")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
is ViewState.Error -> {
|
||||||
|
Log.e(TAG, "Provinces: Error - ${state.message}")
|
||||||
|
binding.progressBarProvinsi.visibility = View.GONE
|
||||||
|
showError("Failed to load provinces: ${state.message}")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun setupCityObserver() {
|
||||||
|
// Same implementation as before
|
||||||
|
registerViewModel.citiesState.observe(viewLifecycleOwner) { state ->
|
||||||
|
when (state) {
|
||||||
|
is ViewState.Loading -> {
|
||||||
|
binding.progressBarKabupaten.visibility = View.VISIBLE
|
||||||
|
}
|
||||||
|
is ViewState.Success -> {
|
||||||
|
Log.d(TAG, "Cities: Success - received ${state.data.size} cities")
|
||||||
|
binding.progressBarKabupaten.visibility = View.GONE
|
||||||
|
cityAdapter.updateData(state.data)
|
||||||
|
Log.d(TAG, "Updated city adapter with ${state.data.size} items")
|
||||||
|
}
|
||||||
|
is ViewState.Error -> {
|
||||||
|
Log.e(TAG, "Cities: Error - ${state.message}")
|
||||||
|
binding.progressBarKabupaten.visibility = View.GONE
|
||||||
|
showError("Failed to load cities: ${state.message}")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun submitAddress() {
|
||||||
|
Log.d(TAG, "submitAddress called")
|
||||||
|
if (!validateAddressForm()) {
|
||||||
|
Log.w(TAG, "Address form validation failed")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
val user = registerViewModel.registeredUser.value
|
||||||
|
if (user == null) {
|
||||||
|
Log.e(TAG, "User data not available")
|
||||||
|
showError("User data not available. Please try again.")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
val userId = user.id
|
||||||
|
Log.d(TAG, "Using user ID: $userId")
|
||||||
|
|
||||||
|
val street = binding.etDetailAlamat.text.toString().trim()
|
||||||
|
val subDistrict = binding.etKecamatan.text.toString().trim()
|
||||||
|
val postalCode = binding.etKodePos.text.toString().trim()
|
||||||
|
val recipient = binding.etNamaPenerima.text.toString().trim()
|
||||||
|
val phone = binding.etNomorHp.text.toString().trim()
|
||||||
|
|
||||||
|
val provinceId = registerViewModel.selectedProvinceId?.toInt() ?: 0
|
||||||
|
val cityId = registerViewModel.selectedCityId?.toInt() ?: 0
|
||||||
|
|
||||||
|
Log.d(TAG, "Address data - Street: $street, SubDistrict: $subDistrict, PostalCode: $postalCode")
|
||||||
|
Log.d(TAG, "Address data - Recipient: $recipient, Phone: $phone")
|
||||||
|
Log.d(TAG, "Address data - ProvinceId: $provinceId, CityId: $cityId")
|
||||||
|
Log.d(TAG, "Address data - Lat: $defaultLatitude, Long: $defaultLongitude")
|
||||||
|
|
||||||
|
// Create address request
|
||||||
|
val addressRequest = CreateAddressRequest(
|
||||||
|
lat = defaultLatitude,
|
||||||
|
long = defaultLongitude,
|
||||||
|
street = street,
|
||||||
|
subDistrict = subDistrict,
|
||||||
|
cityId = cityId,
|
||||||
|
provId = provinceId,
|
||||||
|
postCode = postalCode,
|
||||||
|
detailAddress = street,
|
||||||
|
userId = userId,
|
||||||
|
recipient = recipient,
|
||||||
|
phone = phone,
|
||||||
|
isStoreLocation = false
|
||||||
|
)
|
||||||
|
|
||||||
|
Log.d(TAG, "Address request created: $addressRequest")
|
||||||
|
|
||||||
|
// Show loading
|
||||||
|
binding.progressBar.visibility = View.VISIBLE
|
||||||
|
binding.btnRegister.isEnabled = false
|
||||||
|
// binding.btnSkip.isEnabled = false
|
||||||
|
|
||||||
|
// Submit address
|
||||||
|
registerViewModel.addAddress(addressRequest)
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun validateAddressForm(): Boolean {
|
||||||
|
val street = binding.etDetailAlamat.text.toString().trim()
|
||||||
|
val subDistrict = binding.etKecamatan.text.toString().trim()
|
||||||
|
val postalCode = binding.etKodePos.text.toString().trim()
|
||||||
|
val recipient = binding.etNamaPenerima.text.toString().trim()
|
||||||
|
val phone = binding.etNomorHp.text.toString().trim()
|
||||||
|
|
||||||
|
val provinceId = registerViewModel.selectedProvinceId
|
||||||
|
val cityId = registerViewModel.selectedCityId
|
||||||
|
|
||||||
|
Log.d(TAG, "Validating - Street: $street, SubDistrict: $subDistrict, PostalCode: $postalCode")
|
||||||
|
Log.d(TAG, "Validating - Recipient: $recipient, Phone: $phone")
|
||||||
|
Log.d(TAG, "Validating - ProvinceId: $provinceId, CityId: $cityId")
|
||||||
|
|
||||||
|
|
||||||
|
// Validate required fields
|
||||||
|
if (street.isBlank()) {
|
||||||
|
binding.etDetailAlamat.error = "Alamat tidak boleh kosong"
|
||||||
|
binding.etDetailAlamat.requestFocus()
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
if (recipient.isBlank()) {
|
||||||
|
binding.etNamaPenerima.error = "Nama penerima tidak boleh kosong"
|
||||||
|
binding.etNamaPenerima.requestFocus()
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
if (phone.isBlank()) {
|
||||||
|
binding.etNomorHp.error = "Nomor HP tidak boleh kosong"
|
||||||
|
binding.etNomorHp.requestFocus()
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
if (provinceId == null) {
|
||||||
|
showError("Pilih provinsi terlebih dahulu")
|
||||||
|
binding.autoCompleteProvinsi.requestFocus()
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
if (cityId == null) {
|
||||||
|
showError("Pilih kota/kabupaten terlebih dahulu")
|
||||||
|
binding.autoCompleteKabupaten.requestFocus()
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun observeAddressSubmissionState() {
|
||||||
|
registerViewModel.addressSubmissionState.observe(viewLifecycleOwner) { state ->
|
||||||
|
when (state) {
|
||||||
|
is ViewState.Loading -> {
|
||||||
|
binding.progressBar.visibility = View.VISIBLE
|
||||||
|
binding.btnRegister.isEnabled = false
|
||||||
|
// binding.btnSkip.isEnabled = false
|
||||||
|
}
|
||||||
|
is ViewState.Success -> {
|
||||||
|
Log.d(TAG, "Address submission: Success - ${state.data}")
|
||||||
|
binding.progressBar.visibility = View.GONE
|
||||||
|
showRegistrationSuccess()
|
||||||
|
}
|
||||||
|
is ViewState.Error -> {
|
||||||
|
Log.e(TAG, "Address submission: Error - ${state.message}")
|
||||||
|
binding.progressBar.visibility = View.GONE
|
||||||
|
binding.btnRegister.isEnabled = true
|
||||||
|
// binding.btnSkip.isEnabled = true
|
||||||
|
showError("Failed to add address: ${state.message}")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun showRegistrationSuccess() {
|
||||||
|
// Now we can show the success message for the overall registration process
|
||||||
|
Toast.makeText(requireContext(), "Registration completed successfully!", Toast.LENGTH_LONG).show()
|
||||||
|
|
||||||
|
// Navigate to login screen
|
||||||
|
startActivity(Intent(requireContext(), LoginActivity::class.java))
|
||||||
|
Log.d(TAG, "Navigating to LoginActivity")
|
||||||
|
activity?.finish()
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun showError(message: String) {
|
||||||
|
Toast.makeText(requireContext(), message, Toast.LENGTH_SHORT).show()
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun onDestroyView() {
|
||||||
|
super.onDestroyView()
|
||||||
|
_binding = null
|
||||||
|
}
|
||||||
|
//
|
||||||
|
// // Data classes for province and city
|
||||||
|
// data class Province(val id: String, val name: String)
|
||||||
|
// data class City(val id: String, val name: String)
|
||||||
|
}
|
@ -1,20 +1,49 @@
|
|||||||
package com.alya.ecommerce_serang.utils.viewmodel
|
package com.alya.ecommerce_serang.utils.viewmodel
|
||||||
|
|
||||||
|
import android.content.Context
|
||||||
import android.util.Log
|
import android.util.Log
|
||||||
import androidx.lifecycle.LiveData
|
import androidx.lifecycle.LiveData
|
||||||
import androidx.lifecycle.MutableLiveData
|
import androidx.lifecycle.MutableLiveData
|
||||||
import androidx.lifecycle.ViewModel
|
import androidx.lifecycle.ViewModel
|
||||||
import androidx.lifecycle.viewModelScope
|
import androidx.lifecycle.viewModelScope
|
||||||
|
import com.alya.ecommerce_serang.data.api.dto.CreateAddressRequest
|
||||||
import com.alya.ecommerce_serang.data.api.dto.RegisterRequest
|
import com.alya.ecommerce_serang.data.api.dto.RegisterRequest
|
||||||
import com.alya.ecommerce_serang.data.api.dto.VerifRegisReq
|
import com.alya.ecommerce_serang.data.api.dto.VerifRegisReq
|
||||||
|
import com.alya.ecommerce_serang.data.api.response.auth.LoginResponse
|
||||||
import com.alya.ecommerce_serang.data.api.response.auth.OtpResponse
|
import com.alya.ecommerce_serang.data.api.response.auth.OtpResponse
|
||||||
|
import com.alya.ecommerce_serang.data.api.response.auth.RegisterResponse
|
||||||
|
import com.alya.ecommerce_serang.data.api.response.auth.User
|
||||||
import com.alya.ecommerce_serang.data.api.response.auth.VerifRegisterResponse
|
import com.alya.ecommerce_serang.data.api.response.auth.VerifRegisterResponse
|
||||||
|
import com.alya.ecommerce_serang.data.api.response.customer.order.CitiesItem
|
||||||
|
import com.alya.ecommerce_serang.data.api.response.customer.order.ProvincesItem
|
||||||
|
import com.alya.ecommerce_serang.data.api.retrofit.ApiConfig
|
||||||
|
import com.alya.ecommerce_serang.data.api.retrofit.ApiService
|
||||||
|
import com.alya.ecommerce_serang.data.repository.OrderRepository
|
||||||
import com.alya.ecommerce_serang.data.repository.Result
|
import com.alya.ecommerce_serang.data.repository.Result
|
||||||
import com.alya.ecommerce_serang.data.repository.UserRepository
|
import com.alya.ecommerce_serang.data.repository.UserRepository
|
||||||
|
import com.alya.ecommerce_serang.ui.order.address.ViewState
|
||||||
|
import com.alya.ecommerce_serang.utils.SessionManager
|
||||||
import kotlinx.coroutines.launch
|
import kotlinx.coroutines.launch
|
||||||
|
|
||||||
class RegisterViewModel(private val repository: UserRepository) : ViewModel() {
|
class RegisterViewModel(private val repository: UserRepository, private val orderRepo: OrderRepository, private val context: Context) : ViewModel() {
|
||||||
|
|
||||||
|
private val _loginState = MutableLiveData<Result<LoginResponse>>()
|
||||||
|
val loginState: LiveData<Result<LoginResponse>> get() = _loginState
|
||||||
|
|
||||||
|
// To track if user is authenticated
|
||||||
|
private val _isAuthenticated = MutableLiveData<Boolean>(false)
|
||||||
|
val isAuthenticated: LiveData<Boolean> = _isAuthenticated
|
||||||
|
|
||||||
|
private var _lastCheckedField = MutableLiveData<String>()
|
||||||
|
val lastCheckedField: String
|
||||||
|
get() = _lastCheckedField.value ?: ""
|
||||||
|
|
||||||
|
private val _userData = MutableLiveData<RegisterRequest>()
|
||||||
|
val userData: LiveData<RegisterRequest> = _userData
|
||||||
|
|
||||||
|
// Current step in the registration process
|
||||||
|
private val _currentStep = MutableLiveData<Int>(1)
|
||||||
|
val currentStep: LiveData<Int> = _currentStep
|
||||||
// MutableLiveData for handling register state (Loading, Success, or Error)
|
// MutableLiveData for handling register state (Loading, Success, or Error)
|
||||||
private val _registerState = MutableLiveData<Result<String>>()
|
private val _registerState = MutableLiveData<Result<String>>()
|
||||||
val registerState: LiveData<Result<String>> = _registerState
|
val registerState: LiveData<Result<String>> = _registerState
|
||||||
@ -30,19 +59,55 @@ class RegisterViewModel(private val repository: UserRepository) : ViewModel() {
|
|||||||
private val _message = MutableLiveData<String>()
|
private val _message = MutableLiveData<String>()
|
||||||
val message: LiveData<String> = _message
|
val message: LiveData<String> = _message
|
||||||
|
|
||||||
|
private val _registeredUser = MutableLiveData<User>()
|
||||||
|
val registeredUser: LiveData<User> = _registeredUser
|
||||||
|
|
||||||
|
// For address data
|
||||||
|
var selectedProvinceId: Int? = null
|
||||||
|
var selectedCityId: Int? = null
|
||||||
|
|
||||||
|
// For provinces and cities
|
||||||
|
private val _provincesState = MutableLiveData<ViewState<List<ProvincesItem>>>()
|
||||||
|
val provincesState: LiveData<ViewState<List<ProvincesItem>>> = _provincesState
|
||||||
|
|
||||||
|
private val _citiesState = MutableLiveData<ViewState<List<CitiesItem>>>()
|
||||||
|
val citiesState: LiveData<ViewState<List<CitiesItem>>> = _citiesState
|
||||||
|
|
||||||
|
// For address submission
|
||||||
|
private val _addressSubmissionState = MutableLiveData<ViewState<String>>()
|
||||||
|
val addressSubmissionState: LiveData<ViewState<String>> = _addressSubmissionState
|
||||||
|
|
||||||
|
private val sessionManager by lazy { SessionManager(context) }
|
||||||
|
|
||||||
|
// For authenticated API calls
|
||||||
|
private fun getAuthenticatedApiService(): ApiService {
|
||||||
|
return ApiConfig.getApiService(sessionManager)
|
||||||
|
}
|
||||||
|
|
||||||
|
fun updateUserData(updatedData: RegisterRequest) {
|
||||||
|
_userData.value = updatedData
|
||||||
|
}
|
||||||
|
|
||||||
|
// Set current step
|
||||||
|
fun setStep(step: Int) {
|
||||||
|
_currentStep.value = step
|
||||||
|
}
|
||||||
/**
|
/**
|
||||||
* Function to request OTP by sending an email to the API.
|
* Function to request OTP by sending an email to the API.
|
||||||
* - It sets the OTP state to `Loading` before calling the repository.
|
* - It sets the OTP state to `Loading` before calling the repository.
|
||||||
* - If successful, it updates `_message` with the response message and signals success.
|
* - If successful, it updates `_message` with the response message and signals success.
|
||||||
* - If an error occurs, it updates `_otpState` with `Result.Error` and logs the failure.
|
* - If an error occurs, it updates `_otpState` with `Result.Error` and logs the failure.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
fun requestOtp(email: String) {
|
fun requestOtp(email: String) {
|
||||||
viewModelScope.launch {
|
viewModelScope.launch {
|
||||||
_otpState.value = Result.Loading // Indicating API call in progress
|
_otpState.value = Result.Loading // Indicating API call in progress
|
||||||
|
|
||||||
try {
|
try {
|
||||||
// Call the repository function to request OTP
|
// Call the repository function to request OTP
|
||||||
val response: OtpResponse = repository.requestOtpRep(email)
|
val authenticatedApiService = getAuthenticatedApiService()
|
||||||
|
val authenticatedOrderRepo = UserRepository(authenticatedApiService)
|
||||||
|
val response: OtpResponse = authenticatedOrderRepo.requestOtpRep(email)
|
||||||
|
|
||||||
// Log and store success message
|
// Log and store success message
|
||||||
Log.d("RegisterViewModel", "OTP Response: ${response.message}")
|
Log.d("RegisterViewModel", "OTP Response: ${response.message}")
|
||||||
@ -75,16 +140,43 @@ class RegisterViewModel(private val repository: UserRepository) : ViewModel() {
|
|||||||
|
|
||||||
try {
|
try {
|
||||||
// Call repository function to register the user
|
// Call repository function to register the user
|
||||||
val message = repository.registerUser(request)
|
val response: RegisterResponse = repository.registerUser(request)
|
||||||
|
|
||||||
// Store and display success message
|
Log.d(TAG, "Registration API call successful")
|
||||||
_message.value = message
|
Log.d(TAG, "Response message: ${response.message}")
|
||||||
_registerState.value = Result.Success(message) // Store success result
|
Log.d(TAG, "User ID received: ${response.user.id}")
|
||||||
|
Log.d(TAG, "User details - Name: ${response.user.name}, Email: ${response.user.email}, Username: ${response.user.username}")
|
||||||
|
|
||||||
|
// Store the user data
|
||||||
|
_registeredUser.value = response.user
|
||||||
|
Log.d(TAG, "User data stored in ViewModel")
|
||||||
|
|
||||||
|
// Store success message
|
||||||
|
_message.value = response.message
|
||||||
|
Log.d(TAG, "Success message stored: ${response.message}")
|
||||||
|
|
||||||
|
_registerState.value = Result.Success(response.message)
|
||||||
|
|
||||||
|
// Automatically login after successful registration
|
||||||
|
request.email?.let { email ->
|
||||||
|
|
||||||
|
request.password?.let { password ->
|
||||||
|
Log.d(TAG, "Attempting auto-login with email: $email")
|
||||||
|
|
||||||
|
login(email, password)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
} catch (exception: Exception) {
|
} catch (exception: Exception) {
|
||||||
|
Log.e(TAG, "Registration failed with exception: ${exception.javaClass.simpleName}", exception)
|
||||||
|
Log.e(TAG, "Exception message: ${exception.message}")
|
||||||
|
Log.e(TAG, "Exception cause: ${exception.cause}")
|
||||||
// Handle any errors and update state
|
// Handle any errors and update state
|
||||||
_registerState.value = Result.Error(exception)
|
_registerState.value = Result.Error(exception)
|
||||||
|
|
||||||
_message.value = exception.localizedMessage ?: "Registration failed"
|
_message.value = exception.localizedMessage ?: "Registration failed"
|
||||||
|
Log.d(TAG, "Error message stored: ${exception.localizedMessage ?: "Registration failed"}")
|
||||||
|
|
||||||
|
|
||||||
// Log the error for debugging
|
// Log the error for debugging
|
||||||
Log.e("RegisterViewModel", "User registration failed", exception)
|
Log.e("RegisterViewModel", "User registration failed", exception)
|
||||||
@ -92,7 +184,26 @@ class RegisterViewModel(private val repository: UserRepository) : ViewModel() {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fun login(email: String, password: String) {
|
||||||
|
viewModelScope.launch {
|
||||||
|
_loginState.value = Result.Loading
|
||||||
|
try {
|
||||||
|
val result = repository.login(email, password)
|
||||||
|
_loginState.value = result
|
||||||
|
|
||||||
|
// Update authentication status if login was successful
|
||||||
|
if (result is Result.Success) {
|
||||||
|
_isAuthenticated.value = true
|
||||||
|
}
|
||||||
|
} catch (exception: Exception) {
|
||||||
|
_loginState.value = Result.Error(exception)
|
||||||
|
Log.e("RegisterViewModel", "Login failed", exception)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
fun checkValueReg(request: VerifRegisReq){
|
fun checkValueReg(request: VerifRegisReq){
|
||||||
|
_lastCheckedField.value = request.fieldRegis
|
||||||
viewModelScope.launch {
|
viewModelScope.launch {
|
||||||
try {
|
try {
|
||||||
// Call the repository function to request OTP
|
// Call the repository function to request OTP
|
||||||
@ -111,4 +222,97 @@ class RegisterViewModel(private val repository: UserRepository) : ViewModel() {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fun getProvinces() {
|
||||||
|
_provincesState.value = ViewState.Loading
|
||||||
|
viewModelScope.launch {
|
||||||
|
try {
|
||||||
|
val result = repository.getListProvinces()
|
||||||
|
if (result?.provinces != null) {
|
||||||
|
_provincesState.postValue(ViewState.Success(result.provinces))
|
||||||
|
Log.d(TAG, "Provinces loaded: ${result.provinces.size}")
|
||||||
|
} else {
|
||||||
|
_provincesState.postValue(ViewState.Error("Failed to load provinces"))
|
||||||
|
Log.e(TAG, "Province result was null or empty")
|
||||||
|
}
|
||||||
|
} catch (e: Exception) {
|
||||||
|
_provincesState.postValue(ViewState.Error(e.message ?: "Error loading provinces"))
|
||||||
|
Log.e(TAG, "Error fetching provinces", e)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fun getCities(provinceId: Int) {
|
||||||
|
_citiesState.value = ViewState.Loading
|
||||||
|
viewModelScope.launch {
|
||||||
|
try {
|
||||||
|
|
||||||
|
selectedProvinceId = provinceId
|
||||||
|
val result = repository.getListCities(provinceId)
|
||||||
|
result?.let {
|
||||||
|
_citiesState.postValue(ViewState.Success(it.cities))
|
||||||
|
Log.d(TAG, "Cities loaded for province $provinceId: ${it.cities.size}")
|
||||||
|
} ?: run {
|
||||||
|
_citiesState.postValue(ViewState.Error("Failed to load cities"))
|
||||||
|
Log.e(TAG, "City result was null for province $provinceId")
|
||||||
|
}
|
||||||
|
} catch (e: Exception) {
|
||||||
|
_citiesState.postValue(ViewState.Error(e.message ?: "Error loading cities"))
|
||||||
|
Log.e(TAG, "Error fetching cities for province $provinceId", e)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fun setSelectedProvinceId(id: Int) {
|
||||||
|
selectedProvinceId = id
|
||||||
|
}
|
||||||
|
|
||||||
|
fun setSelectedCityId(id: Int) {
|
||||||
|
selectedCityId = id
|
||||||
|
}
|
||||||
|
|
||||||
|
fun addAddress(request: CreateAddressRequest) {
|
||||||
|
Log.d(TAG, "Starting address submission process")
|
||||||
|
_addressSubmissionState.value = ViewState.Loading
|
||||||
|
viewModelScope.launch {
|
||||||
|
try {
|
||||||
|
val authenticatedApiService = getAuthenticatedApiService()
|
||||||
|
val authenticatedOrderRepo = OrderRepository(authenticatedApiService)
|
||||||
|
Log.d(TAG, "Calling repository.addAddress with request: $request")
|
||||||
|
val result = authenticatedOrderRepo.addAddress(request)
|
||||||
|
|
||||||
|
when (result) {
|
||||||
|
is Result.Success -> {
|
||||||
|
val message = result.data.message
|
||||||
|
Log.d(TAG, "Address added successfully: $message")
|
||||||
|
_addressSubmissionState.postValue(ViewState.Success(message))
|
||||||
|
}
|
||||||
|
is Result.Error -> {
|
||||||
|
val errorMsg = result.exception.message ?: "Unknown error"
|
||||||
|
Log.e(TAG, "Error from repository: $errorMsg", result.exception)
|
||||||
|
_addressSubmissionState.postValue(ViewState.Error(errorMsg))
|
||||||
|
}
|
||||||
|
is Result.Loading -> {
|
||||||
|
Log.d(TAG, "Repository returned Loading state")
|
||||||
|
// We already set Loading at the beginning
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} catch (e: Exception) {
|
||||||
|
Log.e(TAG, "Exception occurred during address submission", e)
|
||||||
|
val errorMessage = e.message ?: "Unknown error occurred"
|
||||||
|
Log.e(TAG, "Error message: $errorMessage")
|
||||||
|
|
||||||
|
// Log the exception stack trace
|
||||||
|
e.printStackTrace()
|
||||||
|
|
||||||
|
_addressSubmissionState.postValue(ViewState.Error(errorMessage))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
companion object {
|
||||||
|
private const val TAG = "RegisterViewModel"
|
||||||
|
}
|
||||||
|
|
||||||
|
//require auth
|
||||||
}
|
}
|
@ -1,256 +1,49 @@
|
|||||||
<?xml version="1.0" encoding="utf-8"?>
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
<ScrollView xmlns:android="http://schemas.android.com/apk/res/android"
|
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
|
||||||
xmlns:app="http://schemas.android.com/apk/res-auto"
|
xmlns:app="http://schemas.android.com/apk/res-auto"
|
||||||
xmlns:tools="http://schemas.android.com/tools"
|
xmlns:tools="http://schemas.android.com/tools"
|
||||||
android:id="@+id/main"
|
android:id="@+id/main"
|
||||||
android:layout_width="match_parent"
|
android:layout_width="match_parent"
|
||||||
android:layout_height="match_parent"
|
android:layout_height="match_parent"
|
||||||
|
android:orientation="vertical"
|
||||||
android:theme="@style/Theme.Ecommerce_serang"
|
android:theme="@style/Theme.Ecommerce_serang"
|
||||||
tools:context=".ui.auth.RegisterActivity">
|
tools:context=".ui.auth.RegisterActivity">
|
||||||
|
|
||||||
<LinearLayout
|
<com.google.android.material.progressindicator.LinearProgressIndicator
|
||||||
|
android:id="@+id/registration_progress"
|
||||||
android:layout_width="match_parent"
|
android:layout_width="match_parent"
|
||||||
android:layout_height="wrap_content"
|
android:layout_height="wrap_content"
|
||||||
android:orientation="vertical"
|
|
||||||
android:layout_marginVertical="16dp"
|
|
||||||
android:layout_marginHorizontal="16dp"
|
android:layout_marginHorizontal="16dp"
|
||||||
android:padding="16dp">
|
android:layout_marginTop="16dp"
|
||||||
|
app:trackThickness="8dp" />
|
||||||
|
|
||||||
<TextView
|
<!-- Step Title -->
|
||||||
android:layout_width="match_parent"
|
<TextView
|
||||||
android:layout_height="wrap_content"
|
android:id="@+id/tv_step_title"
|
||||||
android:text="Buat Akun"
|
android:layout_width="match_parent"
|
||||||
android:textSize="24sp"
|
android:layout_height="wrap_content"
|
||||||
android:textStyle="bold"
|
android:layout_marginHorizontal="16dp"
|
||||||
android:textAlignment="center"
|
android:layout_marginTop="16dp"
|
||||||
android:layout_marginBottom="24dp"/>
|
android:textAlignment="center"
|
||||||
<TextView
|
android:textSize="20sp"
|
||||||
android:layout_width="match_parent"
|
android:textStyle="bold" />
|
||||||
android:layout_height="wrap_content"
|
|
||||||
android:layout_marginTop="4dp"
|
|
||||||
android:fontFamily="@font/dmsans_medium"
|
|
||||||
android:textSize="18sp"
|
|
||||||
android:text="@string/email"/>
|
|
||||||
|
|
||||||
<com.google.android.material.textfield.TextInputLayout
|
<!-- Step Description -->
|
||||||
android:layout_width="match_parent"
|
<TextView
|
||||||
android:layout_height="wrap_content"
|
android:id="@+id/tv_step_description"
|
||||||
android:layout_marginBottom="12dp"
|
android:layout_width="match_parent"
|
||||||
style="@style/Widget.MaterialComponents.TextInputLayout.OutlinedBox">
|
android:layout_height="wrap_content"
|
||||||
|
android:layout_marginHorizontal="16dp"
|
||||||
|
android:layout_marginTop="8dp"
|
||||||
|
android:layout_marginBottom="16dp"
|
||||||
|
android:textAlignment="center" />
|
||||||
|
|
||||||
<com.google.android.material.textfield.TextInputEditText
|
<!-- Fragment Container -->
|
||||||
android:id="@+id/et_email"
|
<FrameLayout
|
||||||
android:layout_width="match_parent"
|
android:id="@+id/fragment_container"
|
||||||
android:layout_height="wrap_content"
|
android:layout_width="match_parent"
|
||||||
android:hint="@string/hint_email"
|
android:layout_height="0dp"
|
||||||
android:inputType="textEmailAddress"/>
|
android:layout_weight="1" />
|
||||||
</com.google.android.material.textfield.TextInputLayout>
|
|
||||||
|
|
||||||
<TextView
|
|
||||||
android:layout_width="match_parent"
|
|
||||||
android:layout_height="wrap_content"
|
|
||||||
android:layout_marginTop="4dp"
|
|
||||||
android:fontFamily="@font/dmsans_medium"
|
|
||||||
android:textSize="18sp"
|
|
||||||
android:text="@string/username"/>
|
|
||||||
|
|
||||||
<com.google.android.material.textfield.TextInputLayout
|
|
||||||
android:layout_width="match_parent"
|
|
||||||
android:layout_height="wrap_content"
|
|
||||||
android:layout_marginBottom="12dp"
|
|
||||||
style="@style/Widget.MaterialComponents.TextInputLayout.OutlinedBox">
|
|
||||||
|
|
||||||
<com.google.android.material.textfield.TextInputEditText
|
|
||||||
android:id="@+id/et_username"
|
|
||||||
android:layout_width="match_parent"
|
|
||||||
android:layout_height="wrap_content"
|
|
||||||
android:hint="@string/hint_username"
|
|
||||||
android:inputType="text"/>
|
|
||||||
</com.google.android.material.textfield.TextInputLayout>
|
|
||||||
|
|
||||||
<TextView
|
|
||||||
android:layout_width="match_parent"
|
|
||||||
android:layout_height="wrap_content"
|
|
||||||
android:layout_marginTop="4dp"
|
|
||||||
android:fontFamily="@font/dmsans_medium"
|
|
||||||
android:textSize="18sp"
|
|
||||||
android:text="@string/full_name"/>
|
|
||||||
|
|
||||||
<com.google.android.material.textfield.TextInputLayout
|
|
||||||
android:layout_width="match_parent"
|
|
||||||
android:layout_height="wrap_content"
|
|
||||||
android:layout_marginBottom="12dp"
|
|
||||||
style="@style/Widget.MaterialComponents.TextInputLayout.OutlinedBox">
|
|
||||||
|
|
||||||
<com.google.android.material.textfield.TextInputEditText
|
|
||||||
android:id="@+id/et_fullname"
|
|
||||||
android:layout_width="match_parent"
|
|
||||||
android:layout_height="wrap_content"
|
|
||||||
android:hint="@string/hint_fullname"
|
|
||||||
android:inputType="text"/>
|
|
||||||
</com.google.android.material.textfield.TextInputLayout>
|
|
||||||
|
|
||||||
<TextView
|
|
||||||
android:layout_width="match_parent"
|
|
||||||
android:layout_height="wrap_content"
|
|
||||||
android:layout_marginTop="4dp"
|
|
||||||
android:fontFamily="@font/dmsans_medium"
|
|
||||||
android:textSize="18sp"
|
|
||||||
android:text="@string/password"/>
|
|
||||||
|
|
||||||
<com.google.android.material.textfield.TextInputLayout
|
|
||||||
android:layout_width="match_parent"
|
|
||||||
android:layout_height="wrap_content"
|
|
||||||
android:layout_marginBottom="12dp"
|
|
||||||
style="@style/Widget.MaterialComponents.TextInputLayout.OutlinedBox"
|
|
||||||
app:passwordToggleEnabled="true">
|
|
||||||
|
|
||||||
<com.google.android.material.textfield.TextInputEditText
|
|
||||||
android:id="@+id/et_password"
|
|
||||||
android:layout_width="match_parent"
|
|
||||||
android:layout_height="wrap_content"
|
|
||||||
android:hint="@string/hint_password"
|
|
||||||
android:inputType="textPassword"/>
|
|
||||||
</com.google.android.material.textfield.TextInputLayout>
|
|
||||||
|
|
||||||
<TextView
|
|
||||||
android:layout_width="match_parent"
|
|
||||||
android:layout_height="wrap_content"
|
|
||||||
android:layout_marginTop="4dp"
|
|
||||||
android:fontFamily="@font/dmsans_medium"
|
|
||||||
android:textSize="18sp"
|
|
||||||
android:text="@string/confirm_password"/>
|
|
||||||
|
|
||||||
<com.google.android.material.textfield.TextInputLayout
|
|
||||||
android:layout_width="match_parent"
|
|
||||||
android:layout_height="wrap_content"
|
|
||||||
android:layout_marginBottom="12dp"
|
|
||||||
style="@style/Widget.MaterialComponents.TextInputLayout.OutlinedBox"
|
|
||||||
app:passwordToggleEnabled="true">
|
|
||||||
|
|
||||||
<com.google.android.material.textfield.TextInputEditText
|
|
||||||
android:id="@+id/et_confirm_password"
|
|
||||||
android:layout_width="match_parent"
|
|
||||||
android:layout_height="wrap_content"
|
|
||||||
android:hint="@string/hint_confirmation_password"
|
|
||||||
android:inputType="textPassword"/>
|
|
||||||
</com.google.android.material.textfield.TextInputLayout>
|
|
||||||
|
|
||||||
<TextView
|
|
||||||
android:layout_width="match_parent"
|
|
||||||
android:layout_height="wrap_content"
|
|
||||||
android:layout_marginTop="4dp"
|
|
||||||
android:fontFamily="@font/dmsans_medium"
|
|
||||||
android:textSize="18sp"
|
|
||||||
android:text="@string/birth_date"/>
|
|
||||||
|
|
||||||
|
|
||||||
<com.google.android.material.textfield.TextInputLayout
|
</LinearLayout>
|
||||||
android:layout_width="match_parent"
|
|
||||||
android:layout_height="wrap_content"
|
|
||||||
android:layout_marginTop="8dp"
|
|
||||||
android:layout_marginBottom="12dp"
|
|
||||||
style="@style/SharpedBorderStyleOutline"
|
|
||||||
app:endIconMode="custom"
|
|
||||||
app:endIconDrawable="@drawable/outline_calendar_today_24">
|
|
||||||
|
|
||||||
<com.google.android.material.textfield.TextInputEditText
|
|
||||||
android:id="@+id/et_birth_date"
|
|
||||||
android:layout_width="match_parent"
|
|
||||||
android:layout_height="wrap_content"
|
|
||||||
android:hint="Pilih tanggal"
|
|
||||||
android:focusable="false"
|
|
||||||
android:clickable="true"
|
|
||||||
android:minHeight="50dp"/>
|
|
||||||
</com.google.android.material.textfield.TextInputLayout>
|
|
||||||
|
|
||||||
<TextView
|
|
||||||
android:layout_width="match_parent"
|
|
||||||
android:layout_height="wrap_content"
|
|
||||||
android:layout_marginTop="4dp"
|
|
||||||
android:fontFamily="@font/dmsans_medium"
|
|
||||||
android:textSize="18sp"
|
|
||||||
android:text="@string/gender"/>
|
|
||||||
|
|
||||||
<com.google.android.material.textfield.TextInputLayout
|
|
||||||
android:layout_width="match_parent"
|
|
||||||
android:layout_height="wrap_content"
|
|
||||||
android:layout_marginBottom="12dp"
|
|
||||||
style="@style/Widget.MaterialComponents.TextInputLayout.OutlinedBox.ExposedDropdownMenu">
|
|
||||||
|
|
||||||
<AutoCompleteTextView
|
|
||||||
android:id="@+id/et_gender"
|
|
||||||
android:layout_width="match_parent"
|
|
||||||
android:layout_height="wrap_content"
|
|
||||||
android:hint="@string/hint_gender"
|
|
||||||
android:inputType="textFilter"/>
|
|
||||||
</com.google.android.material.textfield.TextInputLayout>
|
|
||||||
|
|
||||||
<TextView
|
|
||||||
android:layout_width="match_parent"
|
|
||||||
android:layout_height="wrap_content"
|
|
||||||
android:layout_marginTop="4dp"
|
|
||||||
android:fontFamily="@font/dmsans_medium"
|
|
||||||
android:textSize="18sp"
|
|
||||||
android:text="@string/number_phone"/>
|
|
||||||
|
|
||||||
<com.google.android.material.textfield.TextInputLayout
|
|
||||||
android:layout_width="match_parent"
|
|
||||||
android:layout_height="wrap_content"
|
|
||||||
android:layout_marginBottom="12dp"
|
|
||||||
style="@style/Widget.MaterialComponents.TextInputLayout.OutlinedBox">
|
|
||||||
|
|
||||||
<com.google.android.material.textfield.TextInputEditText
|
|
||||||
android:id="@+id/et_number_phone"
|
|
||||||
android:layout_width="match_parent"
|
|
||||||
android:layout_height="wrap_content"
|
|
||||||
android:hint="@string/hint_number_phone"
|
|
||||||
android:inputType="phone"/>
|
|
||||||
</com.google.android.material.textfield.TextInputLayout>
|
|
||||||
|
|
||||||
<com.google.android.material.button.MaterialButton
|
|
||||||
android:id="@+id/btn_signup"
|
|
||||||
android:layout_width="match_parent"
|
|
||||||
android:layout_height="wrap_content"
|
|
||||||
android:text="@string/signup"
|
|
||||||
android:layout_marginTop="16dp"
|
|
||||||
app:cornerRadius="8dp"/>
|
|
||||||
|
|
||||||
<LinearLayout
|
|
||||||
android:layout_width="match_parent"
|
|
||||||
android:layout_height="wrap_content"
|
|
||||||
android:orientation="horizontal"
|
|
||||||
android:gravity="center"
|
|
||||||
android:layout_marginTop="16dp">
|
|
||||||
|
|
||||||
<TextView
|
|
||||||
android:layout_width="wrap_content"
|
|
||||||
android:layout_height="wrap_content"
|
|
||||||
android:text="@string/no_account"/>
|
|
||||||
|
|
||||||
<TextView
|
|
||||||
android:id="@+id/tv_login_alt"
|
|
||||||
android:layout_width="wrap_content"
|
|
||||||
android:layout_height="wrap_content"
|
|
||||||
android:text="@string/login"
|
|
||||||
android:textColor="@color/blue1"
|
|
||||||
android:textStyle="bold"/>
|
|
||||||
</LinearLayout>
|
|
||||||
|
|
||||||
<ProgressBar
|
|
||||||
android:id="@+id/progressBarOtp"
|
|
||||||
android:layout_width="wrap_content"
|
|
||||||
android:layout_height="wrap_content"
|
|
||||||
android:visibility="gone"
|
|
||||||
android:layout_gravity="center"/>
|
|
||||||
|
|
||||||
<!-- ProgressBar for Registration -->
|
|
||||||
<ProgressBar
|
|
||||||
android:id="@+id/progressBarRegister"
|
|
||||||
android:layout_width="wrap_content"
|
|
||||||
android:layout_height="wrap_content"
|
|
||||||
android:visibility="gone"
|
|
||||||
android:layout_gravity="center"/>
|
|
||||||
</LinearLayout>
|
|
||||||
|
|
||||||
</ScrollView>
|
|
245
app/src/main/res/layout/fragment_register_step1.xml
Normal file
245
app/src/main/res/layout/fragment_register_step1.xml
Normal file
@ -0,0 +1,245 @@
|
|||||||
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
|
<ScrollView xmlns:android="http://schemas.android.com/apk/res/android"
|
||||||
|
xmlns:app="http://schemas.android.com/apk/res-auto"
|
||||||
|
xmlns:tools="http://schemas.android.com/tools"
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="match_parent">
|
||||||
|
|
||||||
|
<LinearLayout
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:orientation="vertical"
|
||||||
|
android:padding="16dp">
|
||||||
|
|
||||||
|
<!-- Account Information Section -->
|
||||||
|
|
||||||
|
<TextView
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:layout_marginTop="4dp"
|
||||||
|
android:fontFamily="@font/dmsans_medium"
|
||||||
|
android:textSize="14sp"
|
||||||
|
android:text="@string/email"/>
|
||||||
|
|
||||||
|
<com.google.android.material.textfield.TextInputLayout
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:layout_marginBottom="12dp"
|
||||||
|
style="@style/Widget.MaterialComponents.TextInputLayout.OutlinedBox">
|
||||||
|
|
||||||
|
<com.google.android.material.textfield.TextInputEditText
|
||||||
|
android:id="@+id/et_email"
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:hint="@string/hint_email"
|
||||||
|
android:inputType="textEmailAddress"/>
|
||||||
|
</com.google.android.material.textfield.TextInputLayout>
|
||||||
|
|
||||||
|
<!-- Username Field -->
|
||||||
|
<TextView
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:layout_marginTop="4dp"
|
||||||
|
android:fontFamily="@font/dmsans_medium"
|
||||||
|
android:textSize="14sp"
|
||||||
|
android:text="@string/username"/>
|
||||||
|
|
||||||
|
<com.google.android.material.textfield.TextInputLayout
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:layout_marginBottom="12dp"
|
||||||
|
style="@style/Widget.MaterialComponents.TextInputLayout.OutlinedBox">
|
||||||
|
|
||||||
|
<com.google.android.material.textfield.TextInputEditText
|
||||||
|
android:id="@+id/et_username"
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:hint="@string/hint_username"
|
||||||
|
android:inputType="text"/>
|
||||||
|
</com.google.android.material.textfield.TextInputLayout>
|
||||||
|
|
||||||
|
<!-- Password Field -->
|
||||||
|
<TextView
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:layout_marginTop="4dp"
|
||||||
|
android:fontFamily="@font/dmsans_medium"
|
||||||
|
android:textSize="14sp"
|
||||||
|
android:text="@string/password"/>
|
||||||
|
|
||||||
|
<com.google.android.material.textfield.TextInputLayout
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:layout_marginBottom="12dp"
|
||||||
|
style="@style/Widget.MaterialComponents.TextInputLayout.OutlinedBox"
|
||||||
|
app:passwordToggleEnabled="true">
|
||||||
|
|
||||||
|
<com.google.android.material.textfield.TextInputEditText
|
||||||
|
android:id="@+id/et_password"
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:hint="@string/hint_password"
|
||||||
|
android:inputType="textPassword"/>
|
||||||
|
</com.google.android.material.textfield.TextInputLayout>
|
||||||
|
|
||||||
|
<!-- Confirm Password Field -->
|
||||||
|
<TextView
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:layout_marginTop="4dp"
|
||||||
|
android:fontFamily="@font/dmsans_medium"
|
||||||
|
android:textSize="14sp"
|
||||||
|
android:text="@string/confirm_password"/>
|
||||||
|
|
||||||
|
<com.google.android.material.textfield.TextInputLayout
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:layout_marginBottom="12dp"
|
||||||
|
style="@style/Widget.MaterialComponents.TextInputLayout.OutlinedBox"
|
||||||
|
app:passwordToggleEnabled="true">
|
||||||
|
|
||||||
|
<com.google.android.material.textfield.TextInputEditText
|
||||||
|
android:id="@+id/et_confirm_password"
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:hint="@string/hint_confirmation_password"
|
||||||
|
android:inputType="textPassword"/>
|
||||||
|
</com.google.android.material.textfield.TextInputLayout>
|
||||||
|
|
||||||
|
<!-- Phone Number Field -->
|
||||||
|
<TextView
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:layout_marginTop="4dp"
|
||||||
|
android:fontFamily="@font/dmsans_medium"
|
||||||
|
android:textSize="14sp"
|
||||||
|
android:text="@string/number_phone"/>
|
||||||
|
|
||||||
|
<com.google.android.material.textfield.TextInputLayout
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:layout_marginBottom="12dp"
|
||||||
|
style="@style/Widget.MaterialComponents.TextInputLayout.OutlinedBox">
|
||||||
|
|
||||||
|
<com.google.android.material.textfield.TextInputEditText
|
||||||
|
android:id="@+id/et_number_phone"
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:hint="@string/hint_number_phone"
|
||||||
|
android:inputType="phone"/>
|
||||||
|
</com.google.android.material.textfield.TextInputLayout>
|
||||||
|
|
||||||
|
<!-- Full Name Field -->
|
||||||
|
<TextView
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:layout_marginTop="4dp"
|
||||||
|
android:fontFamily="@font/dmsans_medium"
|
||||||
|
android:textSize="14sp"
|
||||||
|
android:text="@string/full_name"/>
|
||||||
|
|
||||||
|
<com.google.android.material.textfield.TextInputLayout
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:layout_marginBottom="12dp"
|
||||||
|
style="@style/Widget.MaterialComponents.TextInputLayout.OutlinedBox">
|
||||||
|
|
||||||
|
<com.google.android.material.textfield.TextInputEditText
|
||||||
|
android:id="@+id/et_fullname"
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:hint="@string/hint_fullname"
|
||||||
|
android:inputType="text"/>
|
||||||
|
</com.google.android.material.textfield.TextInputLayout>
|
||||||
|
|
||||||
|
<!-- Birth Date Field -->
|
||||||
|
<TextView
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:layout_marginTop="4dp"
|
||||||
|
android:fontFamily="@font/dmsans_medium"
|
||||||
|
android:textSize="14sp"
|
||||||
|
android:text="@string/birth_date"/>
|
||||||
|
|
||||||
|
<com.google.android.material.textfield.TextInputLayout
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:layout_marginTop="8dp"
|
||||||
|
android:layout_marginBottom="12dp"
|
||||||
|
style="@style/SharpedBorderStyleOutline"
|
||||||
|
app:endIconMode="custom"
|
||||||
|
app:endIconDrawable="@drawable/outline_calendar_today_24">
|
||||||
|
|
||||||
|
<com.google.android.material.textfield.TextInputEditText
|
||||||
|
android:id="@+id/et_birth_date"
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:hint="Pilih tanggal"
|
||||||
|
android:focusable="false"
|
||||||
|
android:clickable="true"
|
||||||
|
android:minHeight="50dp"/>
|
||||||
|
</com.google.android.material.textfield.TextInputLayout>
|
||||||
|
|
||||||
|
<!-- <!– Gender Field –>-->
|
||||||
|
<!-- <TextView-->
|
||||||
|
<!-- android:layout_width="match_parent"-->
|
||||||
|
<!-- android:layout_height="wrap_content"-->
|
||||||
|
<!-- android:layout_marginTop="4dp"-->
|
||||||
|
<!-- android:fontFamily="@font/dmsans_medium"-->
|
||||||
|
<!-- android:textSize="14sp"-->
|
||||||
|
<!-- android:text="@string/gender"/>-->
|
||||||
|
|
||||||
|
<!-- <com.google.android.material.textfield.TextInputLayout-->
|
||||||
|
<!-- android:layout_width="match_parent"-->
|
||||||
|
<!-- android:layout_height="wrap_content"-->
|
||||||
|
<!-- android:layout_marginBottom="12dp"-->
|
||||||
|
<!-- style="@style/Widget.MaterialComponents.TextInputLayout.OutlinedBox.ExposedDropdownMenu">-->
|
||||||
|
|
||||||
|
<!-- <AutoCompleteTextView-->
|
||||||
|
<!-- android:id="@+id/et_gender"-->
|
||||||
|
<!-- android:layout_width="match_parent"-->
|
||||||
|
<!-- android:layout_height="wrap_content"-->
|
||||||
|
<!-- android:hint="@string/hint_gender"-->
|
||||||
|
<!-- android:inputType="textFilter"/>-->
|
||||||
|
<!-- </com.google.android.material.textfield.TextInputLayout>-->
|
||||||
|
|
||||||
|
<!-- Next Button -->
|
||||||
|
<com.google.android.material.button.MaterialButton
|
||||||
|
android:id="@+id/btn_next"
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:text="Next"
|
||||||
|
android:layout_marginTop="16dp"
|
||||||
|
app:cornerRadius="8dp"/>
|
||||||
|
|
||||||
|
<!-- Login Alternative -->
|
||||||
|
<LinearLayout
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:orientation="horizontal"
|
||||||
|
android:gravity="center"
|
||||||
|
android:layout_marginTop="16dp">
|
||||||
|
|
||||||
|
<TextView
|
||||||
|
android:layout_width="wrap_content"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:text="@string/no_account"/>
|
||||||
|
|
||||||
|
<TextView
|
||||||
|
android:id="@+id/tv_login_alt"
|
||||||
|
android:layout_width="wrap_content"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:text="@string/login"
|
||||||
|
android:textColor="@color/blue1"
|
||||||
|
android:textStyle="bold"/>
|
||||||
|
</LinearLayout>
|
||||||
|
|
||||||
|
<!-- Progress Bar -->
|
||||||
|
<ProgressBar
|
||||||
|
android:id="@+id/progress_bar"
|
||||||
|
android:layout_width="wrap_content"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:visibility="gone"
|
||||||
|
android:layout_gravity="center"/>
|
||||||
|
</LinearLayout>
|
||||||
|
</ScrollView>
|
108
app/src/main/res/layout/fragment_register_step2.xml
Normal file
108
app/src/main/res/layout/fragment_register_step2.xml
Normal file
@ -0,0 +1,108 @@
|
|||||||
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
|
<ScrollView xmlns:android="http://schemas.android.com/apk/res/android"
|
||||||
|
xmlns:app="http://schemas.android.com/apk/res-auto"
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="match_parent">
|
||||||
|
|
||||||
|
<LinearLayout
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:orientation="vertical"
|
||||||
|
android:padding="16dp">
|
||||||
|
|
||||||
|
<!-- OTP Verification Image -->
|
||||||
|
<ImageView
|
||||||
|
android:layout_width="120dp"
|
||||||
|
android:layout_height="120dp"
|
||||||
|
android:layout_gravity="center"
|
||||||
|
android:layout_marginTop="16dp"
|
||||||
|
android:src="@drawable/outline_notifications_24"
|
||||||
|
android:contentDescription="OTP Verification" />
|
||||||
|
|
||||||
|
<!-- OTP Input Field -->
|
||||||
|
<TextView
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:layout_marginTop="24dp"
|
||||||
|
android:fontFamily="@font/dmsans_medium"
|
||||||
|
android:textSize="18sp"
|
||||||
|
android:text="Enter Verification Code"
|
||||||
|
android:textAlignment="center" />
|
||||||
|
|
||||||
|
<TextView
|
||||||
|
android:id="@+id/tv_email_sent"
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:layout_marginTop="8dp"
|
||||||
|
android:layout_marginBottom="16dp"
|
||||||
|
android:text="We've sent a verification code to your email"
|
||||||
|
android:textAlignment="center" />
|
||||||
|
|
||||||
|
<!-- OTP Input Layout -->
|
||||||
|
<com.google.android.material.textfield.TextInputLayout
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:layout_marginHorizontal="32dp"
|
||||||
|
android:layout_marginBottom="24dp"
|
||||||
|
style="@style/Widget.MaterialComponents.TextInputLayout.OutlinedBox">
|
||||||
|
|
||||||
|
<com.google.android.material.textfield.TextInputEditText
|
||||||
|
android:id="@+id/et_otp"
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:hint="Enter OTP"
|
||||||
|
android:inputType="number"
|
||||||
|
android:textAlignment="center"
|
||||||
|
android:maxLength="6" />
|
||||||
|
</com.google.android.material.textfield.TextInputLayout>
|
||||||
|
|
||||||
|
<!-- Verify Button -->
|
||||||
|
<com.google.android.material.button.MaterialButton
|
||||||
|
android:id="@+id/btn_verify"
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:text="Verify"
|
||||||
|
app:cornerRadius="8dp" />
|
||||||
|
|
||||||
|
<!-- Resend OTP -->
|
||||||
|
<LinearLayout
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:orientation="horizontal"
|
||||||
|
android:gravity="center"
|
||||||
|
android:layout_marginTop="16dp">
|
||||||
|
|
||||||
|
<TextView
|
||||||
|
android:layout_width="wrap_content"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:text="Didn't receive the code? " />
|
||||||
|
|
||||||
|
<TextView
|
||||||
|
android:id="@+id/tv_resend_otp"
|
||||||
|
android:layout_width="wrap_content"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:text="Resend"
|
||||||
|
android:textColor="@color/blue1"
|
||||||
|
android:textStyle="bold" />
|
||||||
|
</LinearLayout>
|
||||||
|
|
||||||
|
<!-- Timer for resend cooldown -->
|
||||||
|
<TextView
|
||||||
|
android:id="@+id/tv_timer"
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:layout_marginTop="8dp"
|
||||||
|
android:text="Resend available in 00:30"
|
||||||
|
android:textAlignment="center"
|
||||||
|
android:visibility="visible" />
|
||||||
|
|
||||||
|
<!-- Progress Bar -->
|
||||||
|
<ProgressBar
|
||||||
|
android:id="@+id/progress_bar"
|
||||||
|
android:layout_width="wrap_content"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:layout_gravity="center"
|
||||||
|
android:layout_marginTop="16dp"
|
||||||
|
android:visibility="gone" />
|
||||||
|
</LinearLayout>
|
||||||
|
</ScrollView>
|
229
app/src/main/res/layout/fragment_register_step3.xml
Normal file
229
app/src/main/res/layout/fragment_register_step3.xml
Normal file
@ -0,0 +1,229 @@
|
|||||||
|
<?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"
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="match_parent">
|
||||||
|
|
||||||
|
<ScrollView
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="0dp"
|
||||||
|
app:layout_constraintBottom_toTopOf="@id/btn_register"
|
||||||
|
app:layout_constraintTop_toTopOf="parent">
|
||||||
|
|
||||||
|
<LinearLayout
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:orientation="vertical"
|
||||||
|
android:padding="16dp">
|
||||||
|
|
||||||
|
<TextView
|
||||||
|
android:layout_width="wrap_content"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:text="Nama Penerima"
|
||||||
|
android:textColor="@android:color/black"
|
||||||
|
android:textSize="14sp" />
|
||||||
|
|
||||||
|
<EditText
|
||||||
|
android:id="@+id/et_nama_penerima"
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:layout_marginTop="8dp"
|
||||||
|
android:background="@drawable/edit_text_background"
|
||||||
|
android:hint="Isi nama penerima"
|
||||||
|
android:inputType="textPersonName"
|
||||||
|
android:padding="12dp"
|
||||||
|
android:textSize="14sp" />
|
||||||
|
|
||||||
|
<TextView
|
||||||
|
android:layout_width="wrap_content"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:layout_marginTop="16dp"
|
||||||
|
android:text="Nomor Hp"
|
||||||
|
android:textColor="@android:color/black"
|
||||||
|
android:textSize="14sp" />
|
||||||
|
|
||||||
|
<EditText
|
||||||
|
android:id="@+id/et_nomor_hp"
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:layout_marginTop="8dp"
|
||||||
|
android:background="@drawable/edit_text_background"
|
||||||
|
android:hint="Isi nomor handphone aktif"
|
||||||
|
android:inputType="phone"
|
||||||
|
android:padding="12dp"
|
||||||
|
android:textSize="14sp" />
|
||||||
|
|
||||||
|
<TextView
|
||||||
|
android:layout_width="wrap_content"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:layout_marginTop="16dp"
|
||||||
|
android:text="Detail Alamat"
|
||||||
|
android:textColor="@android:color/black"
|
||||||
|
android:textSize="14sp" />
|
||||||
|
|
||||||
|
<EditText
|
||||||
|
android:id="@+id/et_detail_alamat"
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:layout_marginTop="8dp"
|
||||||
|
android:background="@drawable/edit_text_background"
|
||||||
|
android:gravity="top"
|
||||||
|
android:hint="Isi detail alamat (nomor rumah, lantai, dll)"
|
||||||
|
android:inputType="textMultiLine"
|
||||||
|
android:lines="3"
|
||||||
|
android:padding="12dp"
|
||||||
|
android:textSize="14sp" />
|
||||||
|
|
||||||
|
<!-- Provinsi -->
|
||||||
|
<TextView
|
||||||
|
android:layout_width="wrap_content"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:layout_marginTop="16dp"
|
||||||
|
android:text="Provinsi"
|
||||||
|
android:textColor="@android:color/black"
|
||||||
|
android:textSize="14sp" />
|
||||||
|
|
||||||
|
<com.google.android.material.textfield.TextInputLayout
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:layout_marginTop="8dp"
|
||||||
|
android:hint="Pilih Provinsi"
|
||||||
|
style="@style/Widget.MaterialComponents.TextInputLayout.OutlinedBox.ExposedDropdownMenu">
|
||||||
|
|
||||||
|
<AutoCompleteTextView
|
||||||
|
android:id="@+id/autoCompleteProvinsi"
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:inputType="none"
|
||||||
|
android:focusable="false"
|
||||||
|
android:clickable="true"
|
||||||
|
android:padding="12dp"
|
||||||
|
android:textSize="14sp" />
|
||||||
|
</com.google.android.material.textfield.TextInputLayout>
|
||||||
|
|
||||||
|
<ProgressBar
|
||||||
|
android:id="@+id/progress_bar_provinsi"
|
||||||
|
android:layout_width="24dp"
|
||||||
|
android:layout_height="24dp"
|
||||||
|
android:layout_gravity="center_horizontal"
|
||||||
|
android:layout_marginTop="8dp"
|
||||||
|
android:visibility="gone" />
|
||||||
|
|
||||||
|
<!-- Kabupaten / Kota -->
|
||||||
|
<TextView
|
||||||
|
android:layout_width="wrap_content"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:layout_marginTop="16dp"
|
||||||
|
android:text="Kabupaten / Kota"
|
||||||
|
android:textColor="@android:color/black"
|
||||||
|
android:textSize="14sp" />
|
||||||
|
|
||||||
|
<com.google.android.material.textfield.TextInputLayout
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:layout_marginTop="8dp"
|
||||||
|
android:hint="Pilih Kabupaten / Kota"
|
||||||
|
style="@style/Widget.MaterialComponents.TextInputLayout.OutlinedBox.ExposedDropdownMenu">
|
||||||
|
|
||||||
|
<AutoCompleteTextView
|
||||||
|
android:id="@+id/autoCompleteKabupaten"
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:inputType="none"
|
||||||
|
android:focusable="false"
|
||||||
|
android:clickable="true"
|
||||||
|
android:padding="12dp"
|
||||||
|
android:textSize="14sp" />
|
||||||
|
</com.google.android.material.textfield.TextInputLayout>
|
||||||
|
|
||||||
|
<ProgressBar
|
||||||
|
android:id="@+id/progress_bar_kabupaten"
|
||||||
|
android:layout_width="24dp"
|
||||||
|
android:layout_height="24dp"
|
||||||
|
android:layout_gravity="center_horizontal"
|
||||||
|
android:layout_marginTop="8dp"
|
||||||
|
android:visibility="gone" />
|
||||||
|
|
||||||
|
<!-- Kecamatan / Desa -->
|
||||||
|
<TextView
|
||||||
|
android:layout_width="wrap_content"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:layout_marginTop="16dp"
|
||||||
|
android:text="Kecamatan / Desa"
|
||||||
|
android:textColor="@android:color/black"
|
||||||
|
android:textSize="14sp" />
|
||||||
|
|
||||||
|
<com.google.android.material.textfield.TextInputLayout
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:layout_marginTop="8dp"
|
||||||
|
android:hint="Isi Kecamatan / Desa"
|
||||||
|
style="@style/Widget.MaterialComponents.TextInputLayout.OutlinedBox">
|
||||||
|
|
||||||
|
<com.google.android.material.textfield.TextInputEditText
|
||||||
|
android:id="@+id/et_kecamatan"
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:padding="12dp"
|
||||||
|
android:textSize="14sp"
|
||||||
|
android:inputType="textCapWords" />
|
||||||
|
</com.google.android.material.textfield.TextInputLayout>
|
||||||
|
|
||||||
|
<!-- Kode Pos -->
|
||||||
|
<TextView
|
||||||
|
android:layout_width="wrap_content"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:layout_marginTop="16dp"
|
||||||
|
android:text="Kode Pos"
|
||||||
|
android:textColor="@android:color/black"
|
||||||
|
android:textSize="14sp" />
|
||||||
|
|
||||||
|
<EditText
|
||||||
|
android:id="@+id/et_kode_pos"
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:layout_marginTop="8dp"
|
||||||
|
android:background="@drawable/edit_text_background"
|
||||||
|
android:hint="Isi kode pos"
|
||||||
|
android:inputType="number"
|
||||||
|
android:padding="12dp"
|
||||||
|
android:textSize="14sp" />
|
||||||
|
|
||||||
|
<!-- Navigation Button (Previous) -->
|
||||||
|
<Button
|
||||||
|
android:id="@+id/btn_previous"
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:layout_marginTop="24dp"
|
||||||
|
android:background="@drawable/bg_button_outline"
|
||||||
|
android:text="Previous"
|
||||||
|
android:textAllCaps="false"
|
||||||
|
android:textColor="@color/blue1"
|
||||||
|
style="@style/Widget.MaterialComponents.Button.OutlinedButton"/>
|
||||||
|
</LinearLayout>
|
||||||
|
</ScrollView>
|
||||||
|
|
||||||
|
<!-- Register Button -->
|
||||||
|
<Button
|
||||||
|
android:id="@+id/btn_register"
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="56dp"
|
||||||
|
android:layout_margin="16dp"
|
||||||
|
android:background="@drawable/button_address_background"
|
||||||
|
android:text="@string/signup"
|
||||||
|
android:textAllCaps="false"
|
||||||
|
android:textColor="@android:color/white"
|
||||||
|
android:textSize="16sp"
|
||||||
|
app:layout_constraintBottom_toBottomOf="parent" />
|
||||||
|
|
||||||
|
<ProgressBar
|
||||||
|
android:id="@+id/progress_bar"
|
||||||
|
android:layout_width="wrap_content"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:visibility="gone"
|
||||||
|
app:layout_constraintBottom_toBottomOf="parent"
|
||||||
|
app:layout_constraintEnd_toEndOf="parent"
|
||||||
|
app:layout_constraintStart_toStartOf="parent"
|
||||||
|
app:layout_constraintTop_toTopOf="parent" />
|
||||||
|
|
||||||
|
</androidx.constraintlayout.widget.ConstraintLayout>
|
Reference in New Issue
Block a user