WIP
This commit is contained in:
parent
417e9ea322
commit
1da51e223e
@ -1,5 +1,6 @@
|
||||
package com.isolaatti.auth.data
|
||||
|
||||
import com.isolaatti.BuildConfig
|
||||
import com.isolaatti.settings.data.KeyValueDao
|
||||
import com.isolaatti.settings.data.KeyValueEntity
|
||||
import com.isolaatti.auth.data.remote.AuthTokenDto
|
||||
@ -26,7 +27,7 @@ class AuthRepositoryImpl @Inject constructor(
|
||||
): Flow<Resource<Boolean>> = flow {
|
||||
try {
|
||||
val res =
|
||||
authApi.signInWithEmailAndPassword(Credential(email, password)).awaitResponse()
|
||||
authApi.signInWithEmailAndPassword(BuildConfig.clientId, BuildConfig.secret, Credential(email, password)).awaitResponse()
|
||||
|
||||
if(res.isSuccessful) {
|
||||
val dto = res.body()
|
||||
|
||||
@ -11,7 +11,7 @@ interface AuthApi {
|
||||
fun validateTokenUrl(@Header("sessionToken") sessionToken: String): Call<AuthTokenVerificationDto>
|
||||
|
||||
@POST("LogIn")
|
||||
fun signInWithEmailAndPassword(@Body credential: Credential): Call<AuthTokenDto>
|
||||
fun signInWithEmailAndPassword(@Header("apiClientId") clientId: String, @Header("apiClientSecret") clientSecret: String, @Body credential: Credential): Call<AuthTokenDto>
|
||||
|
||||
@GET("LogIn/SignOut")
|
||||
fun signOut(): Call<Nothing>
|
||||
|
||||
@ -2,12 +2,15 @@ package com.isolaatti.sign_up.data
|
||||
|
||||
import com.isolaatti.sign_up.data.dto.CodeValidationDto
|
||||
import com.isolaatti.sign_up.data.dto.DataDto
|
||||
import com.isolaatti.sign_up.data.dto.NameAvailabilityDto
|
||||
import com.isolaatti.sign_up.data.dto.ResultDto
|
||||
import com.isolaatti.sign_up.data.dto.SignUpWithCodeDto
|
||||
import retrofit2.Call
|
||||
import retrofit2.http.Body
|
||||
import retrofit2.http.GET
|
||||
import retrofit2.http.Header
|
||||
import retrofit2.http.POST
|
||||
import retrofit2.http.Query
|
||||
|
||||
interface SignUpApi {
|
||||
|
||||
@ -31,4 +34,9 @@ interface SignUpApi {
|
||||
@Header("clientSecret") apiSecret: String,
|
||||
@Body dto: SignUpWithCodeDto
|
||||
): Call<ResultDto>
|
||||
|
||||
@GET("usernames/check")
|
||||
fun checkNameAvailability(
|
||||
@Query("username") username: String
|
||||
): Call<NameAvailabilityDto>
|
||||
}
|
||||
@ -57,7 +57,23 @@ class SignUpRepositoryImpl @Inject constructor(private val signUpApi: SignUpApi)
|
||||
SignUpWithCodeDto(username, password, displayName, code)
|
||||
).awaitResponse()
|
||||
if(response.isSuccessful){
|
||||
response.body()?.let { GetCodeResult.valueOf(it.result)}
|
||||
response.body()?.let { emit(Resource.Success(SignUpResult.valueOf(it.result)))}
|
||||
} else {
|
||||
emit(Resource.Error(Resource.Error.mapErrorCode(response.code())))
|
||||
}
|
||||
} catch (e: IllegalArgumentException) {
|
||||
emit(Resource.Error(Resource.Error.ErrorType.OtherError, "Could not map response. $e"))
|
||||
} catch(_: Exception) {
|
||||
emit(Resource.Error(Resource.Error.ErrorType.NetworkError))
|
||||
}
|
||||
}
|
||||
|
||||
override fun checkUsernameAvailability(username: String): Flow<Resource<Boolean>> = flow {
|
||||
emit(Resource.Loading())
|
||||
try {
|
||||
val response = signUpApi.checkNameAvailability(username).awaitResponse()
|
||||
if(response.isSuccessful){
|
||||
response.body()?.let { emit(Resource.Success(it.available))}
|
||||
} else {
|
||||
emit(Resource.Error(Resource.Error.mapErrorCode(response.code())))
|
||||
}
|
||||
|
||||
@ -0,0 +1,5 @@
|
||||
package com.isolaatti.sign_up.data.dto
|
||||
|
||||
data class NameAvailabilityDto(
|
||||
val available: Boolean
|
||||
)
|
||||
@ -9,4 +9,5 @@ interface SignUpRepository {
|
||||
fun getCode(email: String): Flow<Resource<GetCodeResult>>
|
||||
fun validateCode(code: String): Flow<Resource<Boolean>>
|
||||
fun signUpWithCode(username: String, displayName: String, password: String, code: String): Flow<Resource<SignUpResult>>
|
||||
fun checkUsernameAvailability(username: String): Flow<Resource<Boolean>>
|
||||
}
|
||||
@ -0,0 +1,81 @@
|
||||
package com.isolaatti.sign_up.presentation
|
||||
|
||||
import androidx.lifecycle.MutableLiveData
|
||||
import androidx.lifecycle.ViewModel
|
||||
import androidx.lifecycle.viewModelScope
|
||||
import com.isolaatti.sign_up.data.SignUpApi
|
||||
import com.isolaatti.sign_up.domain.SignUpRepository
|
||||
import com.isolaatti.sign_up.domain.entity.SignUpResult
|
||||
import com.isolaatti.utils.Resource
|
||||
import dagger.hilt.android.lifecycle.HiltViewModel
|
||||
import kotlinx.coroutines.Dispatchers
|
||||
import kotlinx.coroutines.flow.flowOn
|
||||
import kotlinx.coroutines.flow.launchIn
|
||||
import kotlinx.coroutines.flow.onEach
|
||||
import kotlinx.coroutines.launch
|
||||
import javax.inject.Inject
|
||||
|
||||
@HiltViewModel
|
||||
class MakeAccountViewModel @Inject constructor(private val signUpRepository: SignUpRepository) : ViewModel() {
|
||||
|
||||
val formIsValid: MutableLiveData<Boolean> = MutableLiveData(false)
|
||||
val passwordIsValid: MutableLiveData<Boolean> = MutableLiveData()
|
||||
var usernameIsValid: MutableLiveData<Boolean> = MutableLiveData()
|
||||
val displayNameIsValid: MutableLiveData<Boolean> = MutableLiveData()
|
||||
val signUpResult: MutableLiveData<Resource<SignUpResult>?> = MutableLiveData()
|
||||
var code: String? = null
|
||||
|
||||
private fun validateForm() {
|
||||
formIsValid.value = passwordIsValid.value == true && usernameIsValid.value == true
|
||||
}
|
||||
|
||||
var password: String = ""
|
||||
set(value) {
|
||||
field = value
|
||||
passwordIsValid.value = value.length > 7
|
||||
validateForm()
|
||||
}
|
||||
|
||||
var username: String = ""
|
||||
set(value) {
|
||||
field = value
|
||||
checkUsernameAvailability(value)
|
||||
}
|
||||
|
||||
var displayName: String = ""
|
||||
set(value) {
|
||||
field = value
|
||||
displayNameIsValid.value = value.isNotBlank()
|
||||
}
|
||||
|
||||
private fun checkUsernameAvailability(username: String) {
|
||||
if(username.length < 3) {
|
||||
usernameIsValid.value = false
|
||||
validateForm()
|
||||
return
|
||||
}
|
||||
viewModelScope.launch {
|
||||
signUpRepository.checkUsernameAvailability(username).onEach {
|
||||
when(it) {
|
||||
is Resource.Error -> {}
|
||||
is Resource.Loading -> {}
|
||||
is Resource.Success -> {
|
||||
usernameIsValid.postValue(it.data!!)
|
||||
}
|
||||
}
|
||||
|
||||
}.flowOn(Dispatchers.IO).launchIn(this)
|
||||
}
|
||||
}
|
||||
|
||||
fun makeAccount() {
|
||||
if(code==null) {
|
||||
return
|
||||
}
|
||||
viewModelScope.launch {
|
||||
signUpRepository.signUpWithCode(username, displayName, password, code!!).onEach {
|
||||
signUpResult.postValue(it)
|
||||
}.flowOn(Dispatchers.IO).launchIn(this)
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -1,15 +1,35 @@
|
||||
package com.isolaatti.sign_up.ui
|
||||
|
||||
import android.net.Uri
|
||||
import android.os.Bundle
|
||||
import android.view.LayoutInflater
|
||||
import android.view.View
|
||||
import android.view.ViewGroup
|
||||
import androidx.browser.customtabs.CustomTabsIntent
|
||||
import androidx.core.widget.doOnTextChanged
|
||||
import androidx.fragment.app.Fragment
|
||||
import androidx.fragment.app.activityViewModels
|
||||
import androidx.fragment.app.viewModels
|
||||
import androidx.lifecycle.lifecycleScope
|
||||
import com.isolaatti.BuildConfig
|
||||
import com.isolaatti.R
|
||||
import com.isolaatti.databinding.FragmentMakeAccountBinding
|
||||
import com.isolaatti.sign_up.presentation.MakeAccountViewModel
|
||||
import com.isolaatti.sign_up.presentation.SignUpViewModel
|
||||
import com.isolaatti.utils.Resource
|
||||
import com.isolaatti.utils.textChanges
|
||||
import dagger.hilt.android.AndroidEntryPoint
|
||||
import kotlinx.coroutines.FlowPreview
|
||||
import kotlinx.coroutines.flow.debounce
|
||||
import kotlinx.coroutines.flow.launchIn
|
||||
import kotlinx.coroutines.flow.onEach
|
||||
|
||||
@AndroidEntryPoint
|
||||
class MakeAccountFragment : Fragment() {
|
||||
|
||||
private lateinit var binding: FragmentMakeAccountBinding
|
||||
private val activityViewModel: SignUpViewModel by activityViewModels()
|
||||
private val viewModel: MakeAccountViewModel by viewModels()
|
||||
override fun onCreateView(
|
||||
inflater: LayoutInflater,
|
||||
container: ViewGroup?,
|
||||
@ -19,4 +39,81 @@ class MakeAccountFragment : Fragment() {
|
||||
|
||||
return binding.root
|
||||
}
|
||||
|
||||
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
|
||||
super.onViewCreated(view, savedInstanceState)
|
||||
viewModel.code = activityViewModel.code
|
||||
setupListeners()
|
||||
setupObservers()
|
||||
}
|
||||
|
||||
@OptIn(FlowPreview::class)
|
||||
private fun setupListeners() {
|
||||
binding.readTermsAndConditions.setOnClickListener {
|
||||
val termsAndConditionsCustomTabsIntent = CustomTabsIntent.Builder().build()
|
||||
termsAndConditionsCustomTabsIntent.launchUrl(requireContext(), Uri.parse(BuildConfig.terms))
|
||||
}
|
||||
|
||||
binding.privacyPolicy.setOnClickListener {
|
||||
val termsAndConditionsCustomTabsIntent = CustomTabsIntent.Builder().build()
|
||||
termsAndConditionsCustomTabsIntent.launchUrl(requireContext(), Uri.parse(BuildConfig.privacyPolicy))
|
||||
}
|
||||
binding.password.editText?.doOnTextChanged { text, start, before, count ->
|
||||
viewModel.password = text.toString()
|
||||
}
|
||||
binding.textUsername.editText?.textChanges()?.debounce(300)?.onEach { text ->
|
||||
viewModel.username = text.toString()
|
||||
}?.launchIn(lifecycleScope)
|
||||
binding.textDisplayName.editText?.doOnTextChanged { text, _, _, _ ->
|
||||
viewModel.displayName = text.toString()
|
||||
}
|
||||
binding.signUpButton.setOnClickListener {
|
||||
viewModel.makeAccount()
|
||||
}
|
||||
}
|
||||
|
||||
private fun setupObservers() {
|
||||
viewModel.formIsValid.observe(viewLifecycleOwner) {
|
||||
binding.signUpButton.isEnabled = it
|
||||
}
|
||||
|
||||
viewModel.usernameIsValid.observe(viewLifecycleOwner) {
|
||||
binding.textUsername.error = if(it) {
|
||||
null
|
||||
} else {
|
||||
getText(R.string.username_invalid_feedback)
|
||||
}
|
||||
}
|
||||
|
||||
viewModel.passwordIsValid.observe(viewLifecycleOwner) {
|
||||
binding.password.error = if(it) {
|
||||
null
|
||||
} else {
|
||||
getText(R.string.password_invalid_feedback)
|
||||
}
|
||||
}
|
||||
|
||||
viewModel.displayNameIsValid.observe(viewLifecycleOwner) {
|
||||
binding.textDisplayName.error = if(it){
|
||||
null
|
||||
} else {
|
||||
getString(R.string.display_name_invalid_feedback)
|
||||
}
|
||||
}
|
||||
|
||||
viewModel.signUpResult.observe(viewLifecycleOwner) {
|
||||
|
||||
when(it) {
|
||||
is Resource.Error -> {}
|
||||
is Resource.Loading -> {}
|
||||
is Resource.Success -> {
|
||||
|
||||
|
||||
}
|
||||
null -> {}
|
||||
}
|
||||
|
||||
viewModel.signUpResult.value = null
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -0,0 +1,18 @@
|
||||
package com.isolaatti.utils
|
||||
|
||||
import android.widget.EditText
|
||||
import androidx.core.widget.doOnTextChanged
|
||||
import com.google.android.gms.common.internal.Preconditions.checkMainThread
|
||||
import kotlinx.coroutines.channels.awaitClose
|
||||
import kotlinx.coroutines.flow.Flow
|
||||
import kotlinx.coroutines.flow.callbackFlow
|
||||
import kotlinx.coroutines.flow.onStart
|
||||
|
||||
fun EditText.textChanges(): Flow<CharSequence?> {
|
||||
return callbackFlow {
|
||||
checkMainThread()
|
||||
|
||||
val listener = doOnTextChanged { text, _, _, _ -> trySend(text) }
|
||||
awaitClose { removeTextChangedListener(listener) }
|
||||
}.onStart { emit(text) }
|
||||
}
|
||||
@ -73,10 +73,12 @@
|
||||
style="?attr/textInputOutlinedStyle"
|
||||
android:layout_marginTop="4dp"
|
||||
android:layout_marginHorizontal="16dp"
|
||||
app:endIconMode="password_toggle"
|
||||
android:hint="@string/password">
|
||||
<com.google.android.material.textfield.TextInputEditText
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent"
|
||||
android:inputType="textPassword"
|
||||
android:maxLines="1" />
|
||||
</com.google.android.material.textfield.TextInputLayout>
|
||||
|
||||
@ -106,11 +108,12 @@
|
||||
|
||||
|
||||
<com.google.android.material.button.MaterialButton
|
||||
android:id="@+id/signUpButton"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginTop="16dp"
|
||||
android:layout_marginHorizontal="16dp"
|
||||
android:text="Go"/>
|
||||
android:text="@string/sign_up"/>
|
||||
|
||||
</LinearLayout>
|
||||
|
||||
|
||||
@ -105,9 +105,12 @@
|
||||
<string name="loading">Loading...</string>
|
||||
<string name="unique_username">Unique username</string>
|
||||
<string name="display_name">Display name</string>
|
||||
<string name="create_a_password">Create a fairly password</string>
|
||||
<string name="create_a_password">Create a fairly strong password</string>
|
||||
<string name="let_s_make_your_account">Let\'s make your account</string>
|
||||
<string name="before_making_your_account_please_read_these_legal_terms">Before making your account, please read and accept these legal terms.</string>
|
||||
<string name="before_making_your_account_please_read_these_legal_terms">Before making your account, please read and accept these legal terms. By continuing you are stating that you have read and accepted both.</string>
|
||||
<string name="terms_and_conditions">Terms and conditions</string>
|
||||
<string name="privacy_policy">Privacy policy</string>
|
||||
<string name="username_invalid_feedback">Username is not available or is invalid</string>
|
||||
<string name="password_invalid_feedback">Password must be at least 8 characters long</string>
|
||||
<string name="display_name_invalid_feedback">Please provide a name. This does not have to be unique and can be your real name or not.</string>
|
||||
</resources>
|
||||
Loading…
x
Reference in New Issue
Block a user