This commit is contained in:
Erik Everardo 2023-11-12 23:13:38 -06:00
parent ef83b93fa6
commit 417e9ea322
26 changed files with 747 additions and 26 deletions

View File

@ -35,8 +35,9 @@ class MainActivity : ComponentActivity() {
val currentToken = authRepository.getCurrentToken() val currentToken = authRepository.getCurrentToken()
if(currentToken == null) { if(currentToken == null) {
val loginIntent = Intent(this@MainActivity, LogInActivity::class.java)
signInActivityResult.launch(Intent(this@MainActivity, LogInActivity::class.java)) loginIntent.addFlags(Intent.FLAG_ACTIVITY_CLEAR_TASK or Intent.FLAG_ACTIVITY_NEW_TASK)
signInActivityResult.launch(loginIntent)
} else { } else {
val homeActivityIntent = Intent(this@MainActivity, HomeActivity::class.java) val homeActivityIntent = Intent(this@MainActivity, HomeActivity::class.java)
homeActivityIntent.addFlags(Intent.FLAG_ACTIVITY_CLEAR_TASK or Intent.FLAG_ACTIVITY_NEW_TASK) homeActivityIntent.addFlags(Intent.FLAG_ACTIVITY_CLEAR_TASK or Intent.FLAG_ACTIVITY_NEW_TASK)

View File

@ -58,12 +58,4 @@ class LogInViewModel @Inject constructor(private val authRepository: AuthReposit
}.flowOn(Dispatchers.IO).launchIn(this) }.flowOn(Dispatchers.IO).launchIn(this)
} }
} }
fun signUp() {
}
fun forgotPassword() {
}
} }

View File

@ -0,0 +1,25 @@
package com.isolaatti.sign_up
import com.isolaatti.connectivity.RetrofitClient
import com.isolaatti.sign_up.data.SignUpApi
import com.isolaatti.sign_up.data.SignUpRepositoryImpl
import com.isolaatti.sign_up.domain.SignUpRepository
import dagger.Module
import dagger.Provides
import dagger.hilt.InstallIn
import dagger.hilt.components.SingletonComponent
@Module
@InstallIn(SingletonComponent::class)
class Module {
@Provides
fun provideSignUpApi(retrofitClient: RetrofitClient): SignUpApi {
return retrofitClient.client.create(SignUpApi::class.java)
}
@Provides
fun provideSignUpRepository(signUpApi: SignUpApi): SignUpRepository {
return SignUpRepositoryImpl(signUpApi)
}
}

View File

@ -0,0 +1,34 @@
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.ResultDto
import com.isolaatti.sign_up.data.dto.SignUpWithCodeDto
import retrofit2.Call
import retrofit2.http.Body
import retrofit2.http.Header
import retrofit2.http.POST
interface SignUpApi {
@POST("signUp/get_code")
fun getCode(
@Header("clientId") apiClientId: String,
@Header("clientSecret") apiSecret: String,
@Body email: DataDto
): Call<ResultDto>
@POST("signUp/validate_code")
fun validateCode(
@Header("clientId") apiClientId: String,
@Header("clientSecret") apiSecret: String,
@Body code: DataDto
): Call<CodeValidationDto>
@POST("signUp/sign_up_with_code")
fun signUpWithCode(
@Header("clientId") apiClientId: String,
@Header("clientSecret") apiSecret: String,
@Body dto: SignUpWithCodeDto
): Call<ResultDto>
}

View File

@ -0,0 +1,70 @@
package com.isolaatti.sign_up.data
import com.isolaatti.BuildConfig
import com.isolaatti.sign_up.data.dto.DataDto
import com.isolaatti.sign_up.data.dto.SignUpWithCodeDto
import com.isolaatti.sign_up.domain.SignUpRepository
import com.isolaatti.sign_up.domain.entity.GetCodeResult
import com.isolaatti.sign_up.domain.entity.SignUpResult
import com.isolaatti.utils.Resource
import kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.flow.flow
import retrofit2.awaitResponse
import javax.inject.Inject
class SignUpRepositoryImpl @Inject constructor(private val signUpApi: SignUpApi): SignUpRepository {
override fun getCode(email: String): Flow<Resource<GetCodeResult>> = flow {
emit(Resource.Loading())
try {
val response = signUpApi.getCode(BuildConfig.clientId, BuildConfig.secret, DataDto(email)).awaitResponse()
if(response.isSuccessful){
response.body()?.let { emit(Resource.Success(GetCodeResult.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 validateCode(code: String): Flow<Resource<Boolean>> = flow {
emit(Resource.Loading())
try {
val response = signUpApi.validateCode(BuildConfig.clientId, BuildConfig.secret, DataDto(code)).awaitResponse()
if(response.isSuccessful) {
response.body()?.let { emit(Resource.Success(it.valid)) }
} else {
emit(Resource.Error(Resource.Error.mapErrorCode(response.code())))
}
} catch (_: Exception) {
emit(Resource.Error(Resource.Error.ErrorType.NetworkError))
}
}
override fun signUpWithCode(
username: String,
displayName: String,
password: String,
code: String
): Flow<Resource<SignUpResult>> = flow {
emit(Resource.Loading())
try {
val response = signUpApi.signUpWithCode(
BuildConfig.clientId,
BuildConfig.secret,
SignUpWithCodeDto(username, password, displayName, code)
).awaitResponse()
if(response.isSuccessful){
response.body()?.let { GetCodeResult.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))
}
}
}

View File

@ -0,0 +1,5 @@
package com.isolaatti.sign_up.data.dto
data class CodeValidationDto(
val valid: Boolean
)

View File

@ -0,0 +1,5 @@
package com.isolaatti.sign_up.data.dto
data class DataDto(
val data: String
)

View File

@ -0,0 +1,5 @@
package com.isolaatti.sign_up.data.dto
data class ResultDto(
val result: String
)

View File

@ -0,0 +1,8 @@
package com.isolaatti.sign_up.data.dto
data class SignUpWithCodeDto(
val username: String,
val password: String,
val displayName: String,
val code: String
)

View File

@ -0,0 +1,12 @@
package com.isolaatti.sign_up.domain
import com.isolaatti.sign_up.domain.entity.GetCodeResult
import com.isolaatti.sign_up.domain.entity.SignUpResult
import com.isolaatti.utils.Resource
import kotlinx.coroutines.flow.Flow
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>>
}

View File

@ -0,0 +1,5 @@
package com.isolaatti.sign_up.domain.entity
enum class GetCodeResult {
EmailUsed, Success, EmailValidationError, CodesSentLimitReached
}

View File

@ -0,0 +1,5 @@
package com.isolaatti.sign_up.domain.entity
enum class SignUpResult {
EmailNotAvailable, ValidationProblems, Ok, Error, UsernameUnavailable
}

View File

@ -0,0 +1,43 @@
package com.isolaatti.sign_up.presentation
import androidx.lifecycle.MutableLiveData
import androidx.lifecycle.ViewModel
import androidx.lifecycle.viewModelScope
import com.isolaatti.sign_up.domain.SignUpRepository
import com.isolaatti.sign_up.domain.entity.GetCodeResult
import com.isolaatti.utils.Resource
import com.isolaatti.utils.Validators
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 GetCodeViewModel @Inject constructor(private val signUpRepository: SignUpRepository) : ViewModel() {
var email: String = ""
set(value) {
field = value
emailIsValid.value = Validators.isEmailValid(value)
}
val emailIsValid: MutableLiveData<Boolean> = MutableLiveData(false)
val response: MutableLiveData<Resource<GetCodeResult>?> = MutableLiveData()
fun getCode() {
if(!Validators.isEmailValid(email)) {
return
}
viewModelScope.launch {
signUpRepository.getCode(email).onEach {
response.postValue(it)
}.flowOn(Dispatchers.IO).launchIn(this)
}
}
}

View File

@ -0,0 +1,11 @@
package com.isolaatti.sign_up.presentation
import androidx.lifecycle.ViewModel
import com.isolaatti.sign_up.domain.SignUpRepository
import dagger.hilt.android.lifecycle.HiltViewModel
import javax.inject.Inject
class SignUpViewModel : ViewModel(){
var code: String? = null
}

View File

@ -0,0 +1,37 @@
package com.isolaatti.sign_up.presentation
import androidx.lifecycle.MutableLiveData
import androidx.lifecycle.ViewModel
import androidx.lifecycle.viewModelScope
import com.isolaatti.sign_up.domain.SignUpRepository
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 ValidateCodeViewModel @Inject constructor(private val signUpRepository: SignUpRepository) : ViewModel() {
var code: String? = null
set(value) {
field = value
codeIsValid.value = value?.isNotBlank() == true && value.contains(" ") == false
}
val codeIsValid: MutableLiveData<Boolean> = MutableLiveData(false)
val result: MutableLiveData<Resource<Boolean>?> = MutableLiveData()
fun validateCode() {
if(code != null && code!!.isBlank() || code!!.contains(" ")) {
return
}
viewModelScope.launch {
signUpRepository.validateCode(code!!.trim()).onEach {
result.postValue(it)
}.flowOn(Dispatchers.IO).launchIn(this)
}
}
}

View File

@ -1,21 +1,124 @@
package com.isolaatti.sign_up.ui package com.isolaatti.sign_up.ui
import android.os.Bundle import android.os.Bundle
import android.util.Log
import android.view.LayoutInflater import android.view.LayoutInflater
import android.view.View import android.view.View
import android.view.ViewGroup import android.view.ViewGroup
import androidx.core.widget.doOnTextChanged
import androidx.fragment.app.Fragment import androidx.fragment.app.Fragment
import androidx.fragment.app.activityViewModels
import androidx.fragment.app.viewModels
import androidx.lifecycle.Lifecycle
import androidx.lifecycle.lifecycleScope
import androidx.lifecycle.repeatOnLifecycle
import androidx.navigation.fragment.findNavController
import com.google.android.material.dialog.MaterialAlertDialogBuilder
import com.isolaatti.R
import com.isolaatti.SignUpNavigationDirections
import com.isolaatti.common.ErrorMessageViewModel
import com.isolaatti.databinding.FragmentGetCodeBinding import com.isolaatti.databinding.FragmentGetCodeBinding
import com.isolaatti.sign_up.domain.entity.GetCodeResult
import com.isolaatti.sign_up.presentation.GetCodeViewModel
import com.isolaatti.sign_up.presentation.SignUpViewModel
import com.isolaatti.utils.Resource
import dagger.hilt.android.AndroidEntryPoint
import kotlinx.coroutines.launch
@AndroidEntryPoint
class GetCodeFragment : Fragment() { class GetCodeFragment : Fragment() {
private lateinit var binding: FragmentGetCodeBinding private lateinit var binding: FragmentGetCodeBinding
private val errorViewModel: ErrorMessageViewModel by activityViewModels()
private val viewModel: GetCodeViewModel by viewModels()
override fun onCreateView( override fun onCreateView(
inflater: LayoutInflater, inflater: LayoutInflater,
container: ViewGroup?, container: ViewGroup?,
savedInstanceState: Bundle? savedInstanceState: Bundle?
): View? { ): View {
binding = FragmentGetCodeBinding.inflate(inflater) binding = FragmentGetCodeBinding.inflate(inflater)
return binding.root return binding.root
} }
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
super.onViewCreated(view, savedInstanceState)
setupListeners()
setupObservers()
}
private fun retry() {
// TODO retry here
}
private fun setupListeners() {
binding.goToCodeButton.setOnClickListener {
findNavController().navigate(SignUpNavigationDirections.actionGlobalValidateCodeFragment())
}
binding.backButton.setOnClickListener {
requireActivity().finish()
}
binding.sendButton.setOnClickListener {
binding.sendButton.isEnabled = false
viewModel.getCode()
}
binding.textFieldEmail.editText?.doOnTextChanged { text, _, _, _ ->
viewModel.email = text.toString()
}
}
private fun setupObservers() {
viewLifecycleOwner.lifecycleScope.launch {
repeatOnLifecycle(Lifecycle.State.STARTED) {
errorViewModel.retry.collect {
retry()
}
}
}
viewModel.emailIsValid.observe(viewLifecycleOwner) {
binding.sendButton.isEnabled = it
}
viewModel.response.observe(viewLifecycleOwner) {
binding.sendButton.isEnabled = true
when(it) {
is Resource.Error -> {
errorViewModel.error.postValue(it.errorType)
viewModel.response.value = null
}
is Resource.Loading -> {
viewModel.response.value = null
}
is Resource.Success -> {
viewModel.response.value = null
if(it.data == GetCodeResult.Success) {
findNavController().navigate(GetCodeFragmentDirections.actionGetCodeFragmentToValidateCodeFragment())
return@observe
}
showResultDialog(it.data!!)
}
null -> {}
}
}
}
private fun showResultDialog(result: GetCodeResult) {
val message = when(result) {
GetCodeResult.EmailUsed -> R.string.email_used_when_getting_code
GetCodeResult.EmailValidationError -> R.string.invalid_email
GetCodeResult.CodesSentLimitReached -> R.string.codes_sent_limit_reached
else -> 0
}
MaterialAlertDialogBuilder(requireContext())
.setMessage(message)
.setPositiveButton(R.string.accept, null)
.show()
}
} }

View File

@ -1,6 +1,22 @@
package com.isolaatti.sign_up.ui package com.isolaatti.sign_up.ui
import android.os.Bundle
import android.view.LayoutInflater
import android.view.View
import android.view.ViewGroup
import androidx.fragment.app.Fragment import androidx.fragment.app.Fragment
import com.isolaatti.databinding.FragmentMakeAccountBinding
class MakeAccountFragment : Fragment() { class MakeAccountFragment : Fragment() {
private lateinit var binding: FragmentMakeAccountBinding
override fun onCreateView(
inflater: LayoutInflater,
container: ViewGroup?,
savedInstanceState: Bundle?
): View {
binding = FragmentMakeAccountBinding.inflate(inflater)
return binding.root
}
} }

View File

@ -3,12 +3,17 @@ package com.isolaatti.sign_up.ui
import android.content.Context import android.content.Context
import android.content.Intent import android.content.Intent
import android.os.Bundle import android.os.Bundle
import androidx.activity.viewModels
import androidx.appcompat.app.AppCompatActivity import androidx.appcompat.app.AppCompatActivity
import androidx.core.app.ActivityCompat import com.isolaatti.common.IsolaattiBaseActivity
import com.isolaatti.databinding.ActivitySignUpBinding import com.isolaatti.databinding.ActivitySignUpBinding
import com.isolaatti.sign_up.presentation.SignUpViewModel
import dagger.hilt.android.AndroidEntryPoint
class SignUpActivity : AppCompatActivity() { @AndroidEntryPoint
class SignUpActivity : IsolaattiBaseActivity() {
private lateinit var binding: ActivitySignUpBinding private lateinit var binding: ActivitySignUpBinding
private val viewModel: SignUpViewModel by viewModels()
override fun onCreate(savedInstanceState: Bundle?) { override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState) super.onCreate(savedInstanceState)

View File

@ -1,6 +1,126 @@
package com.isolaatti.sign_up.ui package com.isolaatti.sign_up.ui
import android.os.Bundle
import android.view.LayoutInflater
import android.view.View
import android.view.ViewGroup
import androidx.appcompat.app.AlertDialog
import androidx.core.widget.doOnTextChanged
import androidx.fragment.app.Fragment import androidx.fragment.app.Fragment
import androidx.fragment.app.activityViewModels
import androidx.fragment.app.viewModels
import androidx.lifecycle.Lifecycle
import androidx.lifecycle.lifecycleScope
import androidx.lifecycle.repeatOnLifecycle
import androidx.navigation.fragment.findNavController
import com.google.android.material.dialog.MaterialAlertDialogBuilder
import com.isolaatti.R
import com.isolaatti.common.ErrorMessageViewModel
import com.isolaatti.databinding.FragmentValidateCodeBinding
import com.isolaatti.sign_up.presentation.SignUpViewModel
import com.isolaatti.sign_up.presentation.ValidateCodeViewModel
import com.isolaatti.utils.Resource
import dagger.hilt.android.AndroidEntryPoint
import kotlinx.coroutines.launch
@AndroidEntryPoint
class ValidateCodeFragment : Fragment() { class ValidateCodeFragment : Fragment() {
private lateinit var binding: FragmentValidateCodeBinding
private val activityViewModel: SignUpViewModel by activityViewModels()
private val errorViewModel: ErrorMessageViewModel by activityViewModels()
private val viewModel: ValidateCodeViewModel by viewModels()
private val loadingDialog: AlertDialog by lazy {
MaterialAlertDialogBuilder(requireContext())
.setMessage(R.string.loading)
.show()
}
override fun onCreateView(
inflater: LayoutInflater,
container: ViewGroup?,
savedInstanceState: Bundle?
): View {
binding = FragmentValidateCodeBinding.inflate(inflater)
return binding.root
}
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
super.onViewCreated(view, savedInstanceState)
setupListeners()
setupObservers()
}
private fun retry() {
// TODO retry here
}
private fun setupListeners() {
binding.backButton.setOnClickListener {
findNavController().popBackStack()
}
binding.textFieldCode.editText?.doOnTextChanged { text, _, _, _ ->
viewModel.code = text.toString()
}
binding.acceptButton.setOnClickListener {
viewModel.validateCode()
}
}
private fun setupObservers() {
viewLifecycleOwner.lifecycleScope.launch {
repeatOnLifecycle(Lifecycle.State.STARTED) {
errorViewModel.retry.collect {
retry()
}
}
}
viewModel.codeIsValid.observe(viewLifecycleOwner) {
binding.acceptButton.isEnabled = it
}
viewModel.result.observe(viewLifecycleOwner) {
when(it) {
is Resource.Error -> {
errorViewModel.error.postValue(it.errorType)
}
is Resource.Loading -> {
showLoading(true)
}
is Resource.Success -> {
showLoading(false)
if(it.data!!) {
viewModel.result.value = null
activityViewModel.code = viewModel.code
findNavController().navigate(ValidateCodeFragmentDirections.actionValidateCodeFragmentToMakeAccountFragment())
} else {
showError()
}
}
null -> {}
}
}
}
private fun showLoading(loading: Boolean) {
binding.acceptButton.isEnabled = !loading
binding.textFieldCode.isEnabled = !loading
if(loading) {
loadingDialog.show()
} else {
loadingDialog.dismiss()
}
}
private fun showError() {
MaterialAlertDialogBuilder(requireContext())
.setMessage(R.string.invalid_code)
.setPositiveButton(R.string.accept) {_,_ ->
binding.acceptButton.isEnabled = true
}
.show()
}
} }

View File

@ -3,7 +3,7 @@ package com.isolaatti.utils
sealed class Resource<T> { sealed class Resource<T> {
class Success<T>(val data: T?): Resource<T>() class Success<T>(val data: T?): Resource<T>()
class Loading<T>: Resource<T>() class Loading<T>: Resource<T>()
class Error<T>(val errorType: ErrorType? = null): Resource<T>() { class Error<T>(val errorType: ErrorType? = null, val message: String? = null): Resource<T>() {
enum class ErrorType { enum class ErrorType {
NetworkError, AuthError, NotFoundError, ServerError, OtherError NetworkError, AuthError, NotFoundError, ServerError, OtherError
} }

View File

@ -0,0 +1,9 @@
package com.isolaatti.utils
object Validators {
fun isEmailValid(email: String): Boolean {
val emailRegex = "^[A-Za-z0-9+_.-]+@[A-Za-z0-9.-]+\$".toRegex()
return email.matches(emailRegex)
}
}

View File

@ -25,10 +25,9 @@
android:layout_width="match_parent" android:layout_width="match_parent"
android:layout_height="wrap_content" android:layout_height="wrap_content"
android:layout_margin="24dp" android:layout_margin="24dp"
android:fontFamily="@font/zen_dots_regular"
android:text="@string/app_name" android:text="@string/app_name"
android:textAlignment="center" android:textAlignment="center"
android:textAppearance="?attr/textAppearanceHeadlineLarge" /> style="@style/toolbar_text" />
<TextView <TextView
android:layout_width="match_parent" android:layout_width="match_parent"

View File

@ -20,20 +20,35 @@
android:textAlignment="center" android:textAlignment="center"
android:layout_marginTop="30dp" android:layout_marginTop="30dp"
style="@style/toolbar_text"/> style="@style/toolbar_text"/>
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="horizontal"
android:gravity="center_vertical">
<Button
android:id="@+id/backButton"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
app:icon="@drawable/baseline_arrow_back_24"
style="?attr/materialIconButtonStyle" />
<TextView <TextView
android:layout_width="match_parent" android:layout_width="match_parent"
android:layout_height="wrap_content" android:layout_height="wrap_content"
android:layout_marginHorizontal="16dp" android:layout_marginHorizontal="16dp"
android:textSize="28sp" android:textSize="28sp"
android:lines="2"
android:gravity="center_vertical"
android:text="@string/sign_up"/> android:text="@string/sign_up"/>
</LinearLayout>
<TextView <TextView
android:layout_width="match_parent" android:layout_width="match_parent"
android:layout_height="wrap_content" android:layout_height="wrap_content"
android:text="@string/get_code_info" android:text="@string/get_code_info"
android:layout_marginTop="20dp" android:layout_marginTop="16dp"
android:layout_marginHorizontal="16dp"/> android:layout_marginHorizontal="16dp"/>
<com.google.android.material.button.MaterialButton <com.google.android.material.button.MaterialButton
android:id="@+id/goToCodeButton"
android:layout_width="wrap_content" android:layout_width="wrap_content"
android:layout_height="wrap_content" android:layout_height="wrap_content"
android:layout_gravity="center_horizontal" android:layout_gravity="center_horizontal"
@ -42,6 +57,7 @@
style="@style/Widget.Material3.Button.TextButton"/> style="@style/Widget.Material3.Button.TextButton"/>
<com.google.android.material.textfield.TextInputLayout <com.google.android.material.textfield.TextInputLayout
android:id="@+id/textFieldEmail"
android:layout_width="match_parent" android:layout_width="match_parent"
android:layout_height="wrap_content" android:layout_height="wrap_content"
style="?attr/textInputOutlinedStyle" style="?attr/textInputOutlinedStyle"
@ -54,6 +70,7 @@
android:maxLines="1" /> android:maxLines="1" />
</com.google.android.material.textfield.TextInputLayout> </com.google.android.material.textfield.TextInputLayout>
<com.google.android.material.button.MaterialButton <com.google.android.material.button.MaterialButton
android:id="@+id/sendButton"
android:layout_width="match_parent" android:layout_width="match_parent"
android:layout_height="wrap_content" android:layout_height="wrap_content"
android:layout_marginTop="16dp" android:layout_marginTop="16dp"

View File

@ -1,6 +1,118 @@
<?xml version="1.0" encoding="utf-8"?> <?xml version="1.0" encoding="utf-8"?>
<androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android" <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_width="match_parent"
android:layout_height="match_parent"> android:layout_height="match_parent">
</androidx.constraintlayout.widget.ConstraintLayout> <LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="vertical">
<TextView
android:id="@+id/brand"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:text="@string/app_name"
android:textAlignment="center"
android:layout_marginTop="30dp"
style="@style/toolbar_text"/>
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="horizontal"
android:gravity="center_vertical">
<TextView
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginHorizontal="16dp"
android:textSize="28sp"
android:lines="2"
android:gravity="center_vertical"
android:text="@string/let_s_make_your_account"/>
</LinearLayout>
<com.google.android.material.textfield.TextInputLayout
android:id="@+id/textUsername"
android:layout_width="match_parent"
android:layout_height="wrap_content"
style="?attr/textInputOutlinedStyle"
android:layout_marginTop="4dp"
android:layout_marginHorizontal="16dp"
android:hint="@string/unique_username">
<com.google.android.material.textfield.TextInputEditText
android:layout_width="match_parent"
android:layout_height="match_parent"
android:maxLines="1" />
</com.google.android.material.textfield.TextInputLayout>
<com.google.android.material.textfield.TextInputLayout
android:id="@+id/textDisplayName"
android:layout_width="match_parent"
android:layout_height="wrap_content"
style="?attr/textInputOutlinedStyle"
android:layout_marginTop="4dp"
android:layout_marginHorizontal="16dp"
android:hint="@string/display_name">
<com.google.android.material.textfield.TextInputEditText
android:layout_width="match_parent"
android:layout_height="match_parent"
android:maxLines="1" />
</com.google.android.material.textfield.TextInputLayout>
<TextView
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:text="@string/create_a_password"
android:layout_marginTop="16dp"
android:textSize="18sp"
android:layout_marginHorizontal="16dp"/>
<com.google.android.material.textfield.TextInputLayout
android:id="@+id/password"
android:layout_width="match_parent"
android:layout_height="wrap_content"
style="?attr/textInputOutlinedStyle"
android:layout_marginTop="4dp"
android:layout_marginHorizontal="16dp"
android:hint="@string/password">
<com.google.android.material.textfield.TextInputEditText
android:layout_width="match_parent"
android:layout_height="match_parent"
android:maxLines="1" />
</com.google.android.material.textfield.TextInputLayout>
<TextView
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:text="@string/before_making_your_account_please_read_these_legal_terms"
android:layout_marginTop="16dp"
android:layout_marginHorizontal="16dp"/>
<com.google.android.material.button.MaterialButton
android:id="@+id/readTermsAndConditions"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="@string/terms_and_conditions"
android:layout_marginStart="16dp"
android:layout_marginTop="4dp"
style="@style/Widget.Material3.Button.TextButton"/>
<com.google.android.material.button.MaterialButton
android:id="@+id/privacy_policy"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="@string/privacy_policy"
android:layout_marginStart="16dp"
android:layout_marginTop="2dp"
style="@style/Widget.Material3.Button.TextButton"/>
<com.google.android.material.button.MaterialButton
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginTop="16dp"
android:layout_marginHorizontal="16dp"
android:text="Go"/>
</LinearLayout>
</ScrollView>

View File

@ -1,6 +1,69 @@
<?xml version="1.0" encoding="utf-8"?> <?xml version="1.0" encoding="utf-8"?>
<androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android" <ScrollView xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent" android:layout_width="match_parent"
android:layout_height="match_parent"> android:layout_height="match_parent"
xmlns:app="http://schemas.android.com/apk/res-auto"
android:orientation="vertical">
</androidx.constraintlayout.widget.ConstraintLayout> <LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="vertical">
<TextView
android:id="@+id/brand"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:text="@string/app_name"
android:textAlignment="center"
android:layout_marginTop="30dp"
style="@style/toolbar_text"/>
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="horizontal"
android:gravity="center_vertical">
<Button
android:id="@+id/backButton"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
app:icon="@drawable/baseline_arrow_back_24"
style="?attr/materialIconButtonStyle" />
<TextView
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginHorizontal="16dp"
android:textSize="28sp"
android:text="@string/enter_the_code_we_sent_you_by_email"/>
</LinearLayout>
<TextView
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:text="@string/code_extended_description"
android:layout_marginTop="16dp"
android:layout_marginHorizontal="16dp"/>
<com.google.android.material.textfield.TextInputLayout
android:id="@+id/textFieldCode"
android:layout_width="match_parent"
android:layout_height="wrap_content"
style="?attr/textInputOutlinedStyle"
android:layout_marginTop="4dp"
android:layout_marginHorizontal="16dp"
android:hint="@string/code">
<com.google.android.material.textfield.TextInputEditText
android:layout_width="match_parent"
android:layout_height="match_parent"
android:maxLines="1" />
</com.google.android.material.textfield.TextInputLayout>
<com.google.android.material.button.MaterialButton
android:id="@+id/acceptButton"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="@string/accept"
android:layout_marginTop="16dp"
android:layout_gravity="center_horizontal"/>
</LinearLayout>
</ScrollView>

View File

@ -91,4 +91,23 @@
<string name="get_code_info">The first step to create an account is to provide your email address. We will send a code that you will need to enter in the next step.</string> <string name="get_code_info">The first step to create an account is to provide your email address. We will send a code that you will need to enter in the next step.</string>
<string name="send">Send</string> <string name="send">Send</string>
<string name="already_have_a_code">Already have a code?</string> <string name="already_have_a_code">Already have a code?</string>
<string name="code">Code</string>
<string name="code_extended_description">
Your account is almost there, you just have to enter the code that we sent to your email.
\n\n
If you can\'t see it, check your spam folder. If you still can\'t see it, wait a few minutes for the email to arrive.
</string>
<string name="enter_the_code_we_sent_you_by_email">Enter the code we sent you by email</string>
<string name="email_used_when_getting_code">The email address you want to use to create this account has already been used previously.</string>
<string name="invalid_code">The code you entered is invalid. Some reasons may be that the code is misspelled or it has already expired.</string>
<string name="invalid_email">Email is invalid</string>
<string name="codes_sent_limit_reached">It is not possible to send more codes, you have reached the limit. Contact the administrator.</string>
<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="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="terms_and_conditions">Terms and conditions</string>
<string name="privacy_policy">Privacy policy</string>
</resources> </resources>