From 68caac8afaa5712dece529768186ab3e0151f1ff Mon Sep 17 00:00:00 2001 From: AdrianoCelentano Date: Sat, 2 May 2020 23:18:09 +0200 Subject: [PATCH] refactor Registration --- .../java/app/nexd/android/api/UsersApi.java | 3 +- .../nexd/android/api/model/UpdateUserDto.java | 22 +- .../main/java/app/nexd/android/di/Modules.kt | 8 +- .../ui/auth/register/ErrorTranslator.kt | 31 ++ .../ui/auth/register/InputValidator.kt | 30 ++ .../auth/register/RegisterDetailedFragment.kt | 162 ++++++--- .../register/RegisterDetailedViewModel.kt | 182 +++++----- .../android/ui/auth/register/model/Effect.kt | 8 + .../android/ui/auth/register/model/Event.kt | 11 + .../ui/auth/register/model/ViewState.kt | 36 ++ .../app/nexd/android/ui/common/Constants.kt | 9 +- .../HelperOverviewViewModel.kt | 2 +- .../android/ui/utils/EditTextExtensions.kt | 14 + .../java/app/nexd/android/ui/utils/Either.kt | 6 + .../res/layout/fragment_register_detailed.xml | 315 ++++++++---------- app/src/main/res/values-de/strings.xml | 2 + app/src/main/res/values/strings.xml | 2 + 17 files changed, 500 insertions(+), 343 deletions(-) create mode 100644 app/src/main/java/app/nexd/android/ui/auth/register/ErrorTranslator.kt create mode 100644 app/src/main/java/app/nexd/android/ui/auth/register/InputValidator.kt create mode 100644 app/src/main/java/app/nexd/android/ui/auth/register/model/Effect.kt create mode 100644 app/src/main/java/app/nexd/android/ui/auth/register/model/Event.kt create mode 100644 app/src/main/java/app/nexd/android/ui/auth/register/model/ViewState.kt create mode 100644 app/src/main/java/app/nexd/android/ui/utils/EditTextExtensions.kt create mode 100644 app/src/main/java/app/nexd/android/ui/utils/Either.kt diff --git a/api/src/main/java/app/nexd/android/api/UsersApi.java b/api/src/main/java/app/nexd/android/api/UsersApi.java index c0cd712..07951e2 100644 --- a/api/src/main/java/app/nexd/android/api/UsersApi.java +++ b/api/src/main/java/app/nexd/android/api/UsersApi.java @@ -5,6 +5,7 @@ import app.nexd.android.api.model.UpdateUserDto; import app.nexd.android.api.model.User; import io.reactivex.Observable; +import io.reactivex.Single; import retrofit2.http.GET; import retrofit2.http.Headers; import retrofit2.http.PUT; @@ -66,7 +67,7 @@ Observable userControllerUpdate( "Content-Type:application/json" }) @PUT("users/me") - Observable userControllerUpdateMyself( + Single userControllerUpdateMyself( @retrofit2.http.Body UpdateUserDto updateUserDto ); diff --git a/api/src/main/java/app/nexd/android/api/model/UpdateUserDto.java b/api/src/main/java/app/nexd/android/api/model/UpdateUserDto.java index 54aa632..a70d770 100644 --- a/api/src/main/java/app/nexd/android/api/model/UpdateUserDto.java +++ b/api/src/main/java/app/nexd/android/api/model/UpdateUserDto.java @@ -49,9 +49,9 @@ public class UpdateUserDto { @SerializedName(SERIALIZED_NAME_ZIP_CODE) private String zipCode; - public static final String SERIALIZED_NAME_CITY = "city"; - @SerializedName(SERIALIZED_NAME_CITY) - private String city; + public static final String SERIALIZED_NAME_LOCALITY = "city"; + @SerializedName(SERIALIZED_NAME_LOCALITY) + private String locality; /** * Gets or Sets role @@ -228,7 +228,7 @@ public void setZipCode(String zipCode) { public UpdateUserDto city(String city) { - this.city = city; + this.locality = city; return this; } @@ -239,13 +239,13 @@ public UpdateUserDto city(String city) { @javax.annotation.Nullable @ApiModelProperty(value = "") - public String getCity() { - return city; + public String getLocality() { + return locality; } - public void setCity(String city) { - this.city = city; + public void setLocality(String locality) { + this.locality = locality; } @@ -309,14 +309,14 @@ public boolean equals(java.lang.Object o) { Objects.equals(this.street, updateUserDto.street) && Objects.equals(this.number, updateUserDto.number) && Objects.equals(this.zipCode, updateUserDto.zipCode) && - Objects.equals(this.city, updateUserDto.city) && + Objects.equals(this.locality, updateUserDto.locality) && Objects.equals(this.role, updateUserDto.role) && Objects.equals(this.phoneNumber, updateUserDto.phoneNumber); } @Override public int hashCode() { - return Objects.hash(firstName, lastName, street, number, zipCode, city, role, phoneNumber); + return Objects.hash(firstName, lastName, street, number, zipCode, locality, role, phoneNumber); } @@ -329,7 +329,7 @@ public String toString() { sb.append(" street: ").append(toIndentedString(street)).append("\n"); sb.append(" number: ").append(toIndentedString(number)).append("\n"); sb.append(" zipCode: ").append(toIndentedString(zipCode)).append("\n"); - sb.append(" city: ").append(toIndentedString(city)).append("\n"); + sb.append(" city: ").append(toIndentedString(locality)).append("\n"); sb.append(" role: ").append(toIndentedString(role)).append("\n"); sb.append(" phoneNumber: ").append(toIndentedString(phoneNumber)).append("\n"); sb.append("}"); diff --git a/app/src/main/java/app/nexd/android/di/Modules.kt b/app/src/main/java/app/nexd/android/di/Modules.kt index 93467a9..8786c94 100644 --- a/app/src/main/java/app/nexd/android/di/Modules.kt +++ b/app/src/main/java/app/nexd/android/di/Modules.kt @@ -4,6 +4,8 @@ import app.nexd.android.Api import app.nexd.android.Preferences import app.nexd.android.ui.MainViewModel import app.nexd.android.ui.auth.login.LoginViewModel +import app.nexd.android.ui.auth.register.ErrorTranslator +import app.nexd.android.ui.auth.register.InputValidator import app.nexd.android.ui.auth.register.RegisterDetailedViewModel import app.nexd.android.ui.auth.register.RegisterViewModel import app.nexd.android.ui.helper.checkout.CheckoutViewModel @@ -26,13 +28,17 @@ val appModule = module { single { Api(preferences = get()) } + factory { InputValidator() } + + factory { ErrorTranslator() } + viewModel { MainViewModel(get()) } viewModel { RoleViewModel(get(), get()) } viewModel { RegisterViewModel(get(), get()) } - viewModel { RegisterDetailedViewModel(get(), get()) } + viewModel { RegisterDetailedViewModel(get(), get(), get(), get()) } viewModel { LoginViewModel(get(), get()) } diff --git a/app/src/main/java/app/nexd/android/ui/auth/register/ErrorTranslator.kt b/app/src/main/java/app/nexd/android/ui/auth/register/ErrorTranslator.kt new file mode 100644 index 0000000..02c10ed --- /dev/null +++ b/app/src/main/java/app/nexd/android/ui/auth/register/ErrorTranslator.kt @@ -0,0 +1,31 @@ +package app.nexd.android.ui.auth.register + +import app.nexd.android.R +import app.nexd.android.api.model.BackendErrorEntry +import app.nexd.android.network.BackendError +import app.nexd.android.ui.auth.register.model.Effect +import app.nexd.android.ui.auth.register.model.ErrorState +import app.nexd.android.ui.auth.register.model.ValidationError +import app.nexd.android.ui.utils.Either + +class ErrorTranslator { + + fun translate(error: Throwable): Either { + return when (error) { + is BackendError -> translateBackendError(error) + else -> Either.Left(Effect.ShowErrorMessage(R.string.unknow_error)) + } + } + + private fun translateBackendError(error: BackendError): Either { + val validationErrors = error.errorCodes.map { errorCode -> + when (errorCode) { + BackendErrorEntry.ErrorCodeEnum.VALIDATION_PHONENUMBER_INVALID -> { + ValidationError.PhoneNumber + } + else -> ValidationError.Unknown + } + } + return Either.Right(ErrorState.FormValidationErrors(validationErrors)) + } +} \ No newline at end of file diff --git a/app/src/main/java/app/nexd/android/ui/auth/register/InputValidator.kt b/app/src/main/java/app/nexd/android/ui/auth/register/InputValidator.kt new file mode 100644 index 0000000..0c23b1e --- /dev/null +++ b/app/src/main/java/app/nexd/android/ui/auth/register/InputValidator.kt @@ -0,0 +1,30 @@ +package app.nexd.android.ui.auth.register + +import app.nexd.android.ui.auth.register.model.Event +import app.nexd.android.ui.auth.register.model.ValidationError + +class InputValidator() { + + fun validate( + event: Event.UserRegistrationEvent + ): List { + val errors = mutableListOf() + if (event.locality.isEmpty()) { + errors.add(ValidationError.Locality) + } + if(event.phone.isEmpty()) { + errors.add(ValidationError.PhoneNumber) + } + if(event.zip.isEmpty()) { + errors.add(ValidationError.Zip) + } + if(event.houseNumber.isEmpty()) { + errors.add(ValidationError.HouseNumber) + } + if(event.street.isEmpty()) { + errors.add(ValidationError.Street) + } + return errors + } + +} \ No newline at end of file diff --git a/app/src/main/java/app/nexd/android/ui/auth/register/RegisterDetailedFragment.kt b/app/src/main/java/app/nexd/android/ui/auth/register/RegisterDetailedFragment.kt index b8c527c..51df7c7 100644 --- a/app/src/main/java/app/nexd/android/ui/auth/register/RegisterDetailedFragment.kt +++ b/app/src/main/java/app/nexd/android/ui/auth/register/RegisterDetailedFragment.kt @@ -3,90 +3,142 @@ package app.nexd.android.ui.auth.register import android.content.Intent import android.net.Uri import android.os.Bundle -import android.view.LayoutInflater import android.view.View -import android.view.ViewGroup -import android.view.inputmethod.EditorInfo +import android.widget.EditText +import androidx.annotation.StringRes import androidx.fragment.app.Fragment -import androidx.lifecycle.Observer +import androidx.lifecycle.observe import androidx.navigation.fragment.findNavController import androidx.navigation.ui.AppBarConfiguration import androidx.navigation.ui.setupWithNavController import app.nexd.android.R -import app.nexd.android.databinding.FragmentRegisterDetailedBinding -import app.nexd.android.ui.auth.register.RegisterDetailedViewModel.Progress.* +import app.nexd.android.ui.auth.register.model.Effect +import app.nexd.android.ui.auth.register.model.ErrorState +import app.nexd.android.ui.auth.register.model.Event +import app.nexd.android.ui.auth.register.model.Loading +import app.nexd.android.ui.auth.register.model.ValidationError +import app.nexd.android.ui.auth.register.model.ViewState import app.nexd.android.ui.common.Constants import app.nexd.android.ui.common.DefaultSnackbar +import app.nexd.android.ui.utils.setDoneListener import com.google.android.material.snackbar.Snackbar import kotlinx.android.synthetic.main.fragment_register_detailed.* import org.koin.androidx.viewmodel.ext.android.viewModel -class RegisterDetailedFragment : Fragment() { +class RegisterDetailedFragment : Fragment(R.layout.fragment_register_detailed) { - private val vm: RegisterDetailedViewModel by viewModel() + private val viewModel: RegisterDetailedViewModel by viewModel() - private lateinit var binding: FragmentRegisterDetailedBinding + override fun onViewCreated(view: View, savedInstanceState: Bundle?) { + super.onViewCreated(view, savedInstanceState) + setupNavController() + setupListener() + viewModel.viewState.observe(viewLifecycleOwner, ::renderViewState) + viewModel.effects.observe(viewLifecycleOwner, ::handleEffects) + } - override fun onCreateView( - inflater: LayoutInflater, - container: ViewGroup?, - savedInstanceState: Bundle? - ): View? { - binding = FragmentRegisterDetailedBinding.inflate(inflater, container, false) - binding.viewModel = vm - binding.lifecycleOwner = viewLifecycleOwner - return binding.root + private fun handleEffects(effect: Effect) { + when (effect) { + is Effect.NavigateToRoleView -> navigateToRoleFragment() + is Effect.ShowErrorMessage -> showSnackBar(effect.message) + } } - override fun onViewCreated(view: View, savedInstanceState: Bundle?) { - super.onViewCreated(view, savedInstanceState) + private fun setupListener() { + editText_locality.setDoneListener(::tryRegister) + button_register.setOnClickListener { tryRegister() } + button_dataProtection_detail_registration.setOnClickListener { showPrivacyPolicy() } + } + private fun setupNavController() { with(findNavController()) { val appBarConfiguration = AppBarConfiguration(setOf(R.id.registerDetailedFragment)) register_detailed_toolbar.setupWithNavController(this, appBarConfiguration) } + } - editText_city.setOnEditorActionListener { _, i, _ -> - if (i == EditorInfo.IME_ACTION_DONE) { - vm.setUserDetails() - } - false + private fun navigateToRoleFragment() { + findNavController().navigate(RegisterDetailedFragmentDirections.toRoleFragment()) + } + + private fun showSnackBar(message: Int) { + view?.let { DefaultSnackbar(it, getString(message), Snackbar.LENGTH_SHORT) } + } + + private fun renderViewState(viewState: ViewState) { + renderLoading(viewState.loading) + renderError(viewState.errorState) + } + + private fun renderLoading(loading: Loading) { + when (loading) { + Loading.IsLoading -> showLoading() + Loading.NotLoading -> hideLoading() + } + } + + private fun hideLoading() { + progressBar.visibility = View.GONE + editText_phoneNumber.isEnabled = true + editText_street.isEnabled = true + editText_houseNr.isEnabled = true + editText_zipCode.isEnabled = true + editText_locality.isEnabled = true + } + + private fun showLoading() { + progressBar.visibility = View.VISIBLE + editText_phoneNumber.isEnabled = false + editText_street.isEnabled = false + editText_houseNr.isEnabled = false + editText_zipCode.isEnabled = false + editText_locality.isEnabled = false + } + + private fun renderError(errorState: ErrorState) { + when(errorState) { + is ErrorState.NoError -> hideValidationErrors() + is ErrorState.FormValidationErrors -> renderValidationErrors(errorState.validationErrors) } + } - vm.progress.observe(viewLifecycleOwner, Observer { progress -> - progressBar.visibility = View.GONE - editText_phoneNumber.isEnabled = true - editText_street.isEnabled = true - editText_houseNr.isEnabled = true - editText_zipCode.isEnabled = true - editText_city.isEnabled = true - - when (progress) { - is Idle -> { /* do nothing in idle */ } - is Loading -> { - progressBar.visibility = View.VISIBLE - editText_phoneNumber.isEnabled = false - editText_street.isEnabled = false - editText_houseNr.isEnabled = false - editText_zipCode.isEnabled = false - editText_city.isEnabled = false - } - is Error -> { - progress.message?.let { - DefaultSnackbar(view, it, Snackbar.LENGTH_SHORT) - } - } - is Finished -> { - findNavController().navigate(RegisterDetailedFragmentDirections.toRoleFragment()) - } - } - }) + private fun hideValidationErrors() { + editText_phoneNumber.error = null + editText_houseNr.error = null + editText_zipCode.error = null + editText_locality.text = null + editText_street.text = null + } - button_dataProtection_detail_registration.setOnClickListener { - showPrivacyPolicy() + private fun renderValidationErrors(validationErrors: List) { + hideValidationErrors() + validationErrors.forEach { error -> + when (error) { + is ValidationError.PhoneNumber -> showValidationError(editText_phoneNumber, error.message) + is ValidationError.HouseNumber -> showValidationError(editText_houseNr, error.message) + is ValidationError.Zip -> showValidationError(editText_zipCode, error.message) + is ValidationError.Locality -> showValidationError(editText_locality, error.message) + is ValidationError.Street -> showValidationError(editText_street, error.message) + } } } + private fun showValidationError(editText: EditText, @StringRes message: Int) { + editText.error = getString(message) + } + + private fun tryRegister() { + viewModel.event( + Event.UserRegistrationEvent( + phone = editText_phoneNumber.text.toString(), + zip = editText_zipCode.text.toString(), + houseNumber = editText_houseNr.text.toString(), + locality = editText_locality.text.toString(), + street = editText_street.text.toString() + ) + ) + } + private fun showPrivacyPolicy() { startActivity( Intent( diff --git a/app/src/main/java/app/nexd/android/ui/auth/register/RegisterDetailedViewModel.kt b/app/src/main/java/app/nexd/android/ui/auth/register/RegisterDetailedViewModel.kt index b86bf58..b199d07 100644 --- a/app/src/main/java/app/nexd/android/ui/auth/register/RegisterDetailedViewModel.kt +++ b/app/src/main/java/app/nexd/android/ui/auth/register/RegisterDetailedViewModel.kt @@ -1,127 +1,113 @@ package app.nexd.android.ui.auth.register import android.util.Log -import androidx.annotation.StringRes +import androidx.lifecycle.LiveData import androidx.lifecycle.MutableLiveData import androidx.lifecycle.ViewModel import app.nexd.android.Api import app.nexd.android.Preferences -import app.nexd.android.R -import app.nexd.android.api.model.BackendErrorEntry.ErrorCodeEnum.VALIDATION_PHONENUMBER_INVALID import app.nexd.android.api.model.UpdateUserDto -import app.nexd.android.network.BackendError +import app.nexd.android.ui.auth.register.model.Effect +import app.nexd.android.ui.auth.register.model.ErrorState +import app.nexd.android.ui.auth.register.model.Event +import app.nexd.android.ui.auth.register.model.Loading +import app.nexd.android.ui.auth.register.model.ValidationError +import app.nexd.android.ui.auth.register.model.ViewState +import app.nexd.android.ui.utils.Either +import app.nexd.android.ui.utils.SingleLiveEvent +import io.reactivex.Single import io.reactivex.android.schedulers.AndroidSchedulers +import io.reactivex.disposables.CompositeDisposable +import io.reactivex.rxkotlin.addTo +import io.reactivex.rxkotlin.subscribeBy class RegisterDetailedViewModel( private val api: Api, - private val preferences: Preferences + private val preferences: Preferences, + private val inputValidator: InputValidator, + private val errorTranslator: ErrorTranslator ) : ViewModel() { - sealed class Progress { - object Idle : Progress() - object Loading : Progress() - class Error(@StringRes val message: Int? = null) : Progress() - object Finished : Progress() - } - - val phoneNumber = MutableLiveData("") - - val phoneNumberError = MutableLiveData(0) + val viewState: LiveData + get() = mutableViewState + private var mutableViewState = MutableLiveData(ViewState.init()) - val street = MutableLiveData("") + val effects: LiveData + @Suppress("UNCHECKED_CAST") //this cast is safe SingleLiveEvent is extending LiveData + get() = mutableEffects as LiveData + private var mutableEffects = SingleLiveEvent() - val streetError = MutableLiveData(0) + private val disposable = CompositeDisposable() - val houseNumber = MutableLiveData("") + fun event(event: Event) { + Log.d(this.javaClass.simpleName, "event: $event") + when (event) { + is Event.UserRegistrationEvent -> tryRegisterUser(event) + } + } - val houseNumberError = MutableLiveData(0) + private fun tryRegisterUser(event: Event.UserRegistrationEvent) { + val validationErrors = inputValidator.validate(event) + if (validationErrors.isEmpty()) { + registerUser(event) + } else { + showValidationErrors(validationErrors) + } + } - val zipCode = MutableLiveData("") + private fun registerUser(event: Event.UserRegistrationEvent) { + showLoading() + Single.just(event) + .map { it.toUserUpdateDto() } + .flatMap { api.userControllerUpdateMyself(it) } + .observeOn(AndroidSchedulers.mainThread()) + .subscribeBy( + onSuccess = { completeRegistration() }, + onError = { error -> handleRegistrationError(error) } + ).addTo(disposable) + } - val zipCodeError = MutableLiveData(0) + private fun updateViewState(viewState: ViewState?) { + Log.d(this.javaClass.simpleName, "viewState: $viewState") + mutableViewState.value = viewState + } - val locality = MutableLiveData("") + private fun handleRegistrationError(throwable: Throwable) { + hideLoading() + val error = errorTranslator.translate(throwable) + when (error) { + is Either.Left -> mutableEffects.value = Effect.ShowErrorMessage(error.value.message) + is Either.Right -> updateViewState(mutableViewState.value?.copy(errorState = error.value)) + } + } - val localityError = MutableLiveData(0) + private fun completeRegistration() { + hideLoading() + preferences.registrationComplete = true + } - val progress = MutableLiveData(Progress.Idle) + private fun showValidationErrors(validationErrors: List) { + mutableViewState.value = mutableViewState.value?.copy(errorState = ErrorState.FormValidationErrors(validationErrors), loading = Loading.NotLoading) + } - fun setUserDetails() { - var success = true + private fun hideLoading() { + mutableViewState.value = ViewState(loading = Loading.NotLoading, errorState = ErrorState.NoError) + } - if (phoneNumber.value.isNullOrEmpty()) { - phoneNumberError.value = R.string.error_message_user_detail_field_missing - success = false - } else { - phoneNumberError.value = 0 - } + private fun showLoading() { + updateViewState(mutableViewState.value?.copy(loading = Loading.IsLoading, errorState = ErrorState.NoError)) + } - if (street.value.isNullOrEmpty()) { - streetError.value = R.string.error_message_user_detail_field_missing - success = false - } else - streetError.value = 0 - - if (houseNumber.value.isNullOrEmpty()) { - houseNumberError.value = R.string.error_message_user_detail_field_missing - success = false - } else - houseNumberError.value = 0 - - if (zipCode.value.isNullOrEmpty()) { - zipCodeError.value = R.string.error_message_user_detail_field_missing - success = false - } else - zipCodeError.value = 0 - - if (locality.value.isNullOrEmpty()) { - localityError.value = R.string.error_message_user_detail_field_missing - success = false - } else - localityError.value = 0 - - if (success) { - progress.value = Progress.Loading - with(api) { - userControllerUpdateMyself( - UpdateUserDto() - .phoneNumber(phoneNumber.value) - .street(street.value) - .number(houseNumber.value) - .zipCode(zipCode.value) - .city(locality.value) - ) - .observeOn(AndroidSchedulers.mainThread()) - .subscribe({ - preferences.registrationComplete = true - progress.value = Progress.Finished - }, { error -> - if (error is BackendError) { - error.errorCodes.forEach { - when (it) { - VALIDATION_PHONENUMBER_INVALID -> { - phoneNumberError.value = R.string.error_message_input_validation_phone_number_invalid - } - else -> { - Log.e( - RegisterDetailedViewModel::class.simpleName, - "Unknown error $it", - error - ) - progress.value = Progress.Error() - } - } - } - - progress.value = Progress.Error() - } - - if (progress.value !is Progress.Error) { - progress.value = Progress.Error(R.string.error_message_unknown) - } - }) - } - } + private fun Event.UserRegistrationEvent.toUserUpdateDto(): UpdateUserDto { + return UpdateUserDto() + .phoneNumber(phone) + .street(street) + .number(houseNumber) + .zipCode(zip) + .city(locality) } + override fun onCleared() { + disposable.clear() + } } \ No newline at end of file diff --git a/app/src/main/java/app/nexd/android/ui/auth/register/model/Effect.kt b/app/src/main/java/app/nexd/android/ui/auth/register/model/Effect.kt new file mode 100644 index 0000000..c3ab69a --- /dev/null +++ b/app/src/main/java/app/nexd/android/ui/auth/register/model/Effect.kt @@ -0,0 +1,8 @@ +package app.nexd.android.ui.auth.register.model + +import androidx.annotation.StringRes + +sealed class Effect { + object NavigateToRoleView: Effect() + data class ShowErrorMessage(@StringRes val message: Int): Effect() +} \ No newline at end of file diff --git a/app/src/main/java/app/nexd/android/ui/auth/register/model/Event.kt b/app/src/main/java/app/nexd/android/ui/auth/register/model/Event.kt new file mode 100644 index 0000000..11f6dd8 --- /dev/null +++ b/app/src/main/java/app/nexd/android/ui/auth/register/model/Event.kt @@ -0,0 +1,11 @@ +package app.nexd.android.ui.auth.register.model + +sealed class Event { + data class UserRegistrationEvent( + val phone: String, + val houseNumber: String, + val zip: String, + val street: String, + val locality: String + ): Event() +} \ No newline at end of file diff --git a/app/src/main/java/app/nexd/android/ui/auth/register/model/ViewState.kt b/app/src/main/java/app/nexd/android/ui/auth/register/model/ViewState.kt new file mode 100644 index 0000000..76288f3 --- /dev/null +++ b/app/src/main/java/app/nexd/android/ui/auth/register/model/ViewState.kt @@ -0,0 +1,36 @@ +package app.nexd.android.ui.auth.register.model + +import app.nexd.android.R + +data class ViewState( + val errorState: ErrorState, + val loading: Loading +) { + companion object { + fun init(): ViewState { + return ViewState(ErrorState.NoError, Loading.NotLoading) + } + } +} + +sealed class Loading { + object IsLoading : Loading() + object NotLoading : Loading() +} + +sealed class ErrorState { + data class FormValidationErrors(val validationErrors: List) : ErrorState() + object NoError : ErrorState() +} + +sealed class ValidationError(val message: Int) { + object PhoneNumber : ValidationError(R.string.error_message_input_validation_phone_number_invalid) + object Unknown : ValidationError(R.string.error_message_user_detail_field_missing) + object Password : ValidationError(R.string.error_message_user_detail_field_missing) + object Email : ValidationError(R.string.error_message_user_detail_field_missing) + object Locality : ValidationError(R.string.error_message_user_detail_field_missing) + object Zip : ValidationError(R.string.error_message_user_detail_field_missing) + object HouseNumber : ValidationError(R.string.error_message_user_detail_field_missing) + object Street : ValidationError(R.string.error_message_user_detail_field_missing) + object AccountExist : ValidationError(R.string.error_message_input_validation_phone_number_invalid) +} \ No newline at end of file diff --git a/app/src/main/java/app/nexd/android/ui/common/Constants.kt b/app/src/main/java/app/nexd/android/ui/common/Constants.kt index aad1521..c26bc62 100644 --- a/app/src/main/java/app/nexd/android/ui/common/Constants.kt +++ b/app/src/main/java/app/nexd/android/ui/common/Constants.kt @@ -1,9 +1,6 @@ package app.nexd.android.ui.common -abstract class Constants { - companion object { - const val USER_ME = "me" - - const val PRIVACY_POLICY_URL = "https://www.nexd.app/privacypage" - } +object Constants { + const val USER_ME = "me" + const val PRIVACY_POLICY_URL = "https://www.nexd.app/privacypage" } \ No newline at end of file diff --git a/app/src/main/java/app/nexd/android/ui/helper/requestOverview/HelperOverviewViewModel.kt b/app/src/main/java/app/nexd/android/ui/helper/requestOverview/HelperOverviewViewModel.kt index 8d27ef2..8603e6c 100644 --- a/app/src/main/java/app/nexd/android/ui/helper/requestOverview/HelperOverviewViewModel.kt +++ b/app/src/main/java/app/nexd/android/ui/helper/requestOverview/HelperOverviewViewModel.kt @@ -11,7 +11,7 @@ import app.nexd.android.R import app.nexd.android.api.model.HelpList import app.nexd.android.api.model.HelpRequest import app.nexd.android.api.model.HelpRequestStatus -import app.nexd.android.ui.common.Constants.Companion.USER_ME +import app.nexd.android.ui.common.Constants.USER_ME import io.reactivex.BackpressureStrategy import io.reactivex.android.schedulers.AndroidSchedulers import io.reactivex.disposables.CompositeDisposable diff --git a/app/src/main/java/app/nexd/android/ui/utils/EditTextExtensions.kt b/app/src/main/java/app/nexd/android/ui/utils/EditTextExtensions.kt new file mode 100644 index 0000000..355fcc1 --- /dev/null +++ b/app/src/main/java/app/nexd/android/ui/utils/EditTextExtensions.kt @@ -0,0 +1,14 @@ +package app.nexd.android.ui.utils + +import android.view.inputmethod.EditorInfo +import android.widget.EditText + +fun EditText.setDoneListener(action: () -> Unit) { + setOnEditorActionListener { _, i, _ -> + if (i == EditorInfo.IME_ACTION_DONE) { + action() + } + false + } + +} \ No newline at end of file diff --git a/app/src/main/java/app/nexd/android/ui/utils/Either.kt b/app/src/main/java/app/nexd/android/ui/utils/Either.kt new file mode 100644 index 0000000..2b18dc4 --- /dev/null +++ b/app/src/main/java/app/nexd/android/ui/utils/Either.kt @@ -0,0 +1,6 @@ +package app.nexd.android.ui.utils + +sealed class Either { + class Left(val value: A): Either() + class Right(val value: B): Either() +} \ No newline at end of file diff --git a/app/src/main/res/layout/fragment_register_detailed.xml b/app/src/main/res/layout/fragment_register_detailed.xml index 52b512d..3934821 100644 --- a/app/src/main/res/layout/fragment_register_detailed.xml +++ b/app/src/main/res/layout/fragment_register_detailed.xml @@ -1,198 +1,173 @@ - + android:layout_width="match_parent" + android:layout_height="match_parent" + android:background="@android:color/white"> - - - - + - + android:layout_height="0dp" + app:layout_constraintTop_toBottomOf="@id/register_detailed_toolbar"> - + tools:context=".ui.auth.login.LoginFragment"> - - - + android:layout_alignParentTop="true" + android:layout_marginTop="-6dp" + android:layout_marginBottom="-8dp" + android:indeterminate="true" + android:indeterminateTint="@color/colorPrimaryDark" + android:max="100" + android:padding="0dp" + android:visibility="gone" /> + + - + + + android:layout_marginTop="10dp" + android:layout_marginBottom="20dp" + android:autofillHints="phoneNumber" + android:drawableEnd="@drawable/ic_telephone_num" + android:hint="@string/user_input_details_placeholder_phoneNumber" + android:imeOptions="actionNext" + android:inputType="phone" + android:lines="1" + android:nextFocusDown="@id/editText_street" + android:paddingHorizontal="10dp" /> - - + android:layout_height="wrap_content" + android:orientation="horizontal"> + android:nextFocusRight="@id/editText_houseNr" + android:paddingHorizontal="10dp" /> - - - - - - - - - - - - - - - -