Add email registration screen with AuthService integration
Introduce EmailRegisterScreen (Compose) with contract, validation, and loading UX matching the email sign-in layout. Register submits via AuthService.registerWithEmail; success navigates to Manage and clears the login stack. Email login Register action opens the new route instead of handling registration on the same screen. Made-with: Cursor
This commit is contained in:
@@ -54,6 +54,7 @@ private val LegacyLoginGradientEnd = Color(0xFF5CE4BB)
|
||||
@Composable
|
||||
fun EmailLoginScreen(
|
||||
onLogInToManage: () -> Unit,
|
||||
onOpenRegister: () -> Unit,
|
||||
onBack: () -> Unit,
|
||||
viewModel: EmailLoginViewModel = hiltViewModel()
|
||||
) {
|
||||
@@ -64,6 +65,7 @@ fun EmailLoginScreen(
|
||||
viewModel.actions.collectLatest { action ->
|
||||
when (action) {
|
||||
Action.OpenManage -> onLogInToManage()
|
||||
Action.NavigateToRegister -> onOpenRegister()
|
||||
is Action.ShowMessage -> {
|
||||
Toast.makeText(context, action.message, Toast.LENGTH_SHORT).show()
|
||||
}
|
||||
|
||||
@@ -10,6 +10,7 @@ object EmailLoginScreenContract {
|
||||
|
||||
sealed interface Action {
|
||||
data object OpenManage : Action
|
||||
data object NavigateToRegister : Action
|
||||
data class ShowMessage(val message: String) : Action
|
||||
}
|
||||
|
||||
|
||||
@@ -40,13 +40,13 @@ class EmailLoginViewModel @Inject constructor(
|
||||
is Event.PasswordChanged -> {
|
||||
_uiState.value = _uiState.value.copy(password = event.value)
|
||||
}
|
||||
Event.SignInClicked -> submitEmailAuth(register = false)
|
||||
Event.RegisterClicked -> submitEmailAuth(register = true)
|
||||
Event.SignInClicked -> submitEmailAuth()
|
||||
Event.RegisterClicked -> _actions.tryEmit(Action.NavigateToRegister)
|
||||
Event.BackClicked -> Unit
|
||||
}
|
||||
}
|
||||
|
||||
private fun submitEmailAuth(register: Boolean) {
|
||||
private fun submitEmailAuth() {
|
||||
val email = _uiState.value.email.trim()
|
||||
val password = _uiState.value.password
|
||||
if (email.isEmpty() || password.isEmpty()) {
|
||||
@@ -64,11 +64,7 @@ class EmailLoginViewModel @Inject constructor(
|
||||
viewModelScope.launch {
|
||||
_uiState.value = _uiState.value.copy(isLoading = true)
|
||||
try {
|
||||
val result = if (register) {
|
||||
authService.registerWithEmail(email, password, name = "")
|
||||
} else {
|
||||
authService.loginWithEmailPassword(email, password)
|
||||
}
|
||||
val result = authService.loginWithEmailPassword(email, password)
|
||||
if (result.isSuccess) {
|
||||
_actions.tryEmit(Action.OpenManage)
|
||||
} else {
|
||||
|
||||
@@ -0,0 +1,339 @@
|
||||
package org.db3.airmq.features.login
|
||||
|
||||
import android.widget.Toast
|
||||
import androidx.compose.foundation.background
|
||||
import androidx.compose.foundation.layout.Arrangement
|
||||
import androidx.compose.foundation.layout.Box
|
||||
import androidx.compose.foundation.layout.Column
|
||||
import androidx.compose.foundation.layout.Spacer
|
||||
import androidx.compose.foundation.layout.fillMaxSize
|
||||
import androidx.compose.foundation.layout.fillMaxWidth
|
||||
import androidx.compose.foundation.layout.height
|
||||
import androidx.compose.foundation.layout.padding
|
||||
import androidx.compose.foundation.layout.statusBarsPadding
|
||||
import androidx.compose.foundation.rememberScrollState
|
||||
import androidx.compose.foundation.text.KeyboardOptions
|
||||
import androidx.compose.foundation.verticalScroll
|
||||
import androidx.compose.material3.CircularProgressIndicator
|
||||
import androidx.compose.material3.Icon
|
||||
import androidx.compose.material3.IconButton
|
||||
import androidx.compose.material3.OutlinedTextField
|
||||
import androidx.compose.material3.OutlinedTextFieldDefaults
|
||||
import androidx.compose.material3.Text
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.runtime.LaunchedEffect
|
||||
import androidx.compose.runtime.getValue
|
||||
import androidx.compose.ui.Alignment
|
||||
import androidx.compose.ui.Modifier
|
||||
import androidx.compose.ui.geometry.Offset
|
||||
import androidx.compose.ui.graphics.Brush
|
||||
import androidx.compose.ui.graphics.Color
|
||||
import androidx.compose.ui.platform.LocalContext
|
||||
import androidx.compose.ui.res.painterResource
|
||||
import androidx.compose.ui.res.stringResource
|
||||
import androidx.compose.ui.text.font.FontWeight
|
||||
import androidx.compose.ui.text.input.ImeAction
|
||||
import androidx.compose.ui.text.input.KeyboardType
|
||||
import androidx.compose.ui.text.input.PasswordVisualTransformation
|
||||
import androidx.compose.ui.text.style.TextAlign
|
||||
import androidx.compose.ui.tooling.preview.Preview
|
||||
import androidx.compose.ui.unit.dp
|
||||
import androidx.compose.ui.unit.sp
|
||||
import androidx.compose.runtime.collectAsState
|
||||
import androidx.hilt.navigation.compose.hiltViewModel
|
||||
import kotlinx.coroutines.flow.collectLatest
|
||||
import org.db3.airmq.R
|
||||
import org.db3.airmq.features.common.AirMQOutlinedLightButton
|
||||
import org.db3.airmq.features.login.EmailRegisterScreenContract.Action
|
||||
import org.db3.airmq.features.login.EmailRegisterScreenContract.Event
|
||||
import org.db3.airmq.features.login.EmailRegisterScreenContract.State
|
||||
import org.db3.airmq.ui.theme.AirMQTheme
|
||||
|
||||
private val LegacyLoginGradientStart = Color(0xFF449CF5)
|
||||
private val LegacyLoginGradientEnd = Color(0xFF5CE4BB)
|
||||
|
||||
@Composable
|
||||
fun EmailRegisterScreen(
|
||||
onRegisterSuccess: () -> Unit,
|
||||
onBack: () -> Unit,
|
||||
viewModel: EmailRegisterViewModel = hiltViewModel()
|
||||
) {
|
||||
val uiState by viewModel.uiState.collectAsState()
|
||||
val context = LocalContext.current
|
||||
|
||||
LaunchedEffect(viewModel) {
|
||||
viewModel.actions.collectLatest { action ->
|
||||
when (action) {
|
||||
Action.OpenManage -> onRegisterSuccess()
|
||||
is Action.ShowMessage -> {
|
||||
Toast.makeText(context, action.message, Toast.LENGTH_SHORT).show()
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
EmailRegisterScreenContent(
|
||||
uiState = uiState,
|
||||
onEvent = viewModel::onEvent,
|
||||
onBack = onBack
|
||||
)
|
||||
}
|
||||
|
||||
@Composable
|
||||
private fun EmailRegisterScreenContent(
|
||||
uiState: State,
|
||||
onEvent: (Event) -> Unit,
|
||||
onBack: () -> Unit
|
||||
) {
|
||||
val fieldEnabled = !uiState.isLoading
|
||||
val scroll = rememberScrollState()
|
||||
|
||||
Box(
|
||||
modifier = Modifier
|
||||
.fillMaxSize()
|
||||
.background(
|
||||
brush = Brush.linearGradient(
|
||||
colorStops = arrayOf(
|
||||
0.0f to LegacyLoginGradientStart,
|
||||
0.35f to LegacyLoginGradientStart,
|
||||
1.0f to LegacyLoginGradientEnd
|
||||
),
|
||||
start = Offset(0f, 0f),
|
||||
end = Offset(1200f, 1200f)
|
||||
)
|
||||
)
|
||||
) {
|
||||
Column(
|
||||
modifier = Modifier
|
||||
.fillMaxSize()
|
||||
.statusBarsPadding()
|
||||
.padding(horizontal = 40.dp),
|
||||
horizontalAlignment = Alignment.CenterHorizontally
|
||||
) {
|
||||
Spacer(modifier = Modifier.height(16.dp))
|
||||
|
||||
Box(
|
||||
modifier = Modifier.fillMaxWidth(),
|
||||
contentAlignment = Alignment.CenterStart
|
||||
) {
|
||||
IconButton(onClick = onBack, enabled = fieldEnabled) {
|
||||
Icon(
|
||||
painter = painterResource(id = R.drawable.ic_arrow_back),
|
||||
contentDescription = stringResource(id = R.string.content_back),
|
||||
tint = Color.White
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
Spacer(modifier = Modifier.height(24.dp))
|
||||
|
||||
Column(
|
||||
modifier = Modifier
|
||||
.weight(1f)
|
||||
.fillMaxWidth()
|
||||
.verticalScroll(scroll),
|
||||
verticalArrangement = Arrangement.spacedBy(16.dp),
|
||||
horizontalAlignment = Alignment.CenterHorizontally
|
||||
) {
|
||||
Text(
|
||||
text = stringResource(id = R.string.text_register_email_title),
|
||||
color = Color.White,
|
||||
fontSize = 36.sp,
|
||||
fontWeight = FontWeight.Light,
|
||||
textAlign = TextAlign.Center,
|
||||
modifier = Modifier.fillMaxWidth()
|
||||
)
|
||||
|
||||
Spacer(modifier = Modifier.height(8.dp))
|
||||
|
||||
OutlinedTextField(
|
||||
value = uiState.name,
|
||||
onValueChange = { onEvent(Event.NameChanged(it)) },
|
||||
singleLine = true,
|
||||
enabled = fieldEnabled,
|
||||
modifier = Modifier.fillMaxWidth(),
|
||||
isError = uiState.nameError != null,
|
||||
supportingText = uiState.nameError?.let { err ->
|
||||
{ Text(err, color = Color.White.copy(alpha = 0.9f)) }
|
||||
},
|
||||
placeholder = {
|
||||
Text(
|
||||
stringResource(id = R.string.hint_register_name),
|
||||
color = Color.White.copy(alpha = 0.7f)
|
||||
)
|
||||
},
|
||||
keyboardOptions = KeyboardOptions(
|
||||
keyboardType = KeyboardType.Text,
|
||||
imeAction = ImeAction.Next
|
||||
),
|
||||
colors = legacyLoginFieldColors()
|
||||
)
|
||||
|
||||
OutlinedTextField(
|
||||
value = uiState.email,
|
||||
onValueChange = { onEvent(Event.EmailChanged(it)) },
|
||||
singleLine = true,
|
||||
enabled = fieldEnabled,
|
||||
modifier = Modifier.fillMaxWidth(),
|
||||
isError = uiState.emailError != null,
|
||||
supportingText = uiState.emailError?.let { err ->
|
||||
{ Text(err, color = Color.White.copy(alpha = 0.9f)) }
|
||||
},
|
||||
placeholder = {
|
||||
Text(
|
||||
stringResource(id = R.string.hint_email),
|
||||
color = Color.White.copy(alpha = 0.7f)
|
||||
)
|
||||
},
|
||||
keyboardOptions = KeyboardOptions(
|
||||
keyboardType = KeyboardType.Email,
|
||||
imeAction = ImeAction.Next
|
||||
),
|
||||
colors = legacyLoginFieldColors()
|
||||
)
|
||||
|
||||
OutlinedTextField(
|
||||
value = uiState.password,
|
||||
onValueChange = { onEvent(Event.PasswordChanged(it)) },
|
||||
singleLine = true,
|
||||
enabled = fieldEnabled,
|
||||
modifier = Modifier.fillMaxWidth(),
|
||||
isError = uiState.passwordError != null,
|
||||
supportingText = uiState.passwordError?.let { err ->
|
||||
{ Text(err, color = Color.White.copy(alpha = 0.9f)) }
|
||||
},
|
||||
placeholder = {
|
||||
Text(
|
||||
stringResource(id = R.string.hint_password),
|
||||
color = Color.White.copy(alpha = 0.7f)
|
||||
)
|
||||
},
|
||||
visualTransformation = PasswordVisualTransformation(),
|
||||
keyboardOptions = KeyboardOptions(
|
||||
keyboardType = KeyboardType.Password,
|
||||
imeAction = ImeAction.Next
|
||||
),
|
||||
colors = legacyLoginFieldColors()
|
||||
)
|
||||
|
||||
OutlinedTextField(
|
||||
value = uiState.passwordConfirm,
|
||||
onValueChange = { onEvent(Event.PasswordConfirmChanged(it)) },
|
||||
singleLine = true,
|
||||
enabled = fieldEnabled,
|
||||
modifier = Modifier.fillMaxWidth(),
|
||||
isError = uiState.passwordConfirmError != null,
|
||||
supportingText = uiState.passwordConfirmError?.let { err ->
|
||||
{ Text(err, color = Color.White.copy(alpha = 0.9f)) }
|
||||
},
|
||||
placeholder = {
|
||||
Text(
|
||||
stringResource(id = R.string.hint_password_confirm),
|
||||
color = Color.White.copy(alpha = 0.7f)
|
||||
)
|
||||
},
|
||||
visualTransformation = PasswordVisualTransformation(),
|
||||
keyboardOptions = KeyboardOptions(
|
||||
keyboardType = KeyboardType.Password,
|
||||
imeAction = ImeAction.Done
|
||||
),
|
||||
colors = legacyLoginFieldColors()
|
||||
)
|
||||
|
||||
if (uiState.isLoading) {
|
||||
CircularProgressIndicator(color = Color.White)
|
||||
} else {
|
||||
AirMQOutlinedLightButton(
|
||||
text = stringResource(id = R.string.button_register),
|
||||
modifier = Modifier.fillMaxWidth(),
|
||||
onClick = { onEvent(Event.RegisterClicked) }
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
Spacer(modifier = Modifier.height(24.dp))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Composable
|
||||
private fun legacyLoginFieldColors() = OutlinedTextFieldDefaults.colors(
|
||||
focusedTextColor = Color.White,
|
||||
unfocusedTextColor = Color.White,
|
||||
cursorColor = Color.White,
|
||||
focusedBorderColor = Color.White.copy(alpha = 0.8f),
|
||||
unfocusedBorderColor = Color.White.copy(alpha = 0.55f),
|
||||
focusedContainerColor = Color.White.copy(alpha = 0.1f),
|
||||
unfocusedContainerColor = Color.White.copy(alpha = 0.05f),
|
||||
errorBorderColor = Color.White.copy(alpha = 0.95f),
|
||||
errorCursorColor = Color.White,
|
||||
errorSupportingTextColor = Color.White.copy(alpha = 0.95f)
|
||||
)
|
||||
|
||||
@Preview(showBackground = true)
|
||||
@Composable
|
||||
private fun EmailRegisterScreenPreviewEmpty() {
|
||||
AirMQTheme {
|
||||
EmailRegisterScreenContent(
|
||||
uiState = EmailRegisterScreenContract.previewState(),
|
||||
onEvent = {},
|
||||
onBack = {}
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
@Preview(showBackground = true)
|
||||
@Composable
|
||||
private fun EmailRegisterScreenPreviewFilled() {
|
||||
AirMQTheme {
|
||||
EmailRegisterScreenContent(
|
||||
uiState = EmailRegisterScreenContract.previewState(
|
||||
name = "Alex",
|
||||
email = "alex@example.com",
|
||||
password = "secret1",
|
||||
passwordConfirm = "secret1"
|
||||
),
|
||||
onEvent = {},
|
||||
onBack = {}
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
@Preview(showBackground = true)
|
||||
@Composable
|
||||
private fun EmailRegisterScreenPreviewLoading() {
|
||||
AirMQTheme {
|
||||
EmailRegisterScreenContent(
|
||||
uiState = EmailRegisterScreenContract.previewState(
|
||||
name = "Alex",
|
||||
email = "alex@example.com",
|
||||
password = "secret1",
|
||||
passwordConfirm = "secret1",
|
||||
isLoading = true
|
||||
),
|
||||
onEvent = {},
|
||||
onBack = {}
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
@Preview(showBackground = true)
|
||||
@Composable
|
||||
private fun EmailRegisterScreenPreviewErrors() {
|
||||
AirMQTheme {
|
||||
EmailRegisterScreenContent(
|
||||
uiState = EmailRegisterScreenContract.previewState(
|
||||
name = "",
|
||||
email = "bad",
|
||||
password = "12",
|
||||
passwordConfirm = "34",
|
||||
nameError = "Enter your name",
|
||||
emailError = "Invalid email",
|
||||
passwordError = "Too short",
|
||||
passwordConfirmError = "Does not match"
|
||||
),
|
||||
onEvent = {},
|
||||
onBack = {}
|
||||
)
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,52 @@
|
||||
package org.db3.airmq.features.login
|
||||
|
||||
object EmailRegisterScreenContract {
|
||||
|
||||
data class State(
|
||||
val name: String = "",
|
||||
val email: String = "",
|
||||
val password: String = "",
|
||||
val passwordConfirm: String = "",
|
||||
val isLoading: Boolean = false,
|
||||
val nameError: String? = null,
|
||||
val emailError: String? = null,
|
||||
val passwordError: String? = null,
|
||||
val passwordConfirmError: String? = null
|
||||
)
|
||||
|
||||
fun previewState(
|
||||
name: String = "",
|
||||
email: String = "",
|
||||
password: String = "",
|
||||
passwordConfirm: String = "",
|
||||
isLoading: Boolean = false,
|
||||
nameError: String? = null,
|
||||
emailError: String? = null,
|
||||
passwordError: String? = null,
|
||||
passwordConfirmError: String? = null
|
||||
): State = State(
|
||||
name = name,
|
||||
email = email,
|
||||
password = password,
|
||||
passwordConfirm = passwordConfirm,
|
||||
isLoading = isLoading,
|
||||
nameError = nameError,
|
||||
emailError = emailError,
|
||||
passwordError = passwordError,
|
||||
passwordConfirmError = passwordConfirmError
|
||||
)
|
||||
|
||||
sealed interface Action {
|
||||
data object OpenManage : Action
|
||||
data class ShowMessage(val message: String) : Action
|
||||
}
|
||||
|
||||
sealed interface Event {
|
||||
data class NameChanged(val value: String) : Event
|
||||
data class EmailChanged(val value: String) : Event
|
||||
data class PasswordChanged(val value: String) : Event
|
||||
data class PasswordConfirmChanged(val value: String) : Event
|
||||
data object RegisterClicked : Event
|
||||
data object BackClicked : Event
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,122 @@
|
||||
package org.db3.airmq.features.login
|
||||
|
||||
import android.content.Context
|
||||
import android.util.Patterns
|
||||
import androidx.lifecycle.ViewModel
|
||||
import androidx.lifecycle.viewModelScope
|
||||
import dagger.hilt.android.lifecycle.HiltViewModel
|
||||
import dagger.hilt.android.qualifiers.ApplicationContext
|
||||
import javax.inject.Inject
|
||||
import kotlinx.coroutines.flow.MutableSharedFlow
|
||||
import kotlinx.coroutines.flow.MutableStateFlow
|
||||
import kotlinx.coroutines.flow.SharedFlow
|
||||
import kotlinx.coroutines.flow.StateFlow
|
||||
import kotlinx.coroutines.flow.asSharedFlow
|
||||
import kotlinx.coroutines.flow.asStateFlow
|
||||
import kotlinx.coroutines.launch
|
||||
import org.db3.airmq.R
|
||||
import org.db3.airmq.features.login.EmailRegisterScreenContract.Action
|
||||
import org.db3.airmq.features.login.EmailRegisterScreenContract.Event
|
||||
import org.db3.airmq.features.login.EmailRegisterScreenContract.State
|
||||
import org.db3.airmq.sdk.auth.AuthService
|
||||
|
||||
private const val MinPasswordLength = 6
|
||||
|
||||
@HiltViewModel
|
||||
class EmailRegisterViewModel @Inject constructor(
|
||||
@ApplicationContext private val appContext: Context,
|
||||
private val authService: AuthService
|
||||
) : ViewModel() {
|
||||
|
||||
private val _uiState = MutableStateFlow(State())
|
||||
val uiState: StateFlow<State> = _uiState.asStateFlow()
|
||||
|
||||
private val _actions = MutableSharedFlow<Action>(extraBufferCapacity = 1)
|
||||
val actions: SharedFlow<Action> = _actions.asSharedFlow()
|
||||
|
||||
fun onEvent(event: Event) {
|
||||
when (event) {
|
||||
is Event.NameChanged -> {
|
||||
_uiState.value = _uiState.value.copy(name = event.value, nameError = null)
|
||||
}
|
||||
is Event.EmailChanged -> {
|
||||
_uiState.value = _uiState.value.copy(email = event.value, emailError = null)
|
||||
}
|
||||
is Event.PasswordChanged -> {
|
||||
_uiState.value = _uiState.value.copy(password = event.value, passwordError = null)
|
||||
}
|
||||
is Event.PasswordConfirmChanged -> {
|
||||
_uiState.value = _uiState.value.copy(
|
||||
passwordConfirm = event.value,
|
||||
passwordConfirmError = null
|
||||
)
|
||||
}
|
||||
Event.RegisterClicked -> register()
|
||||
Event.BackClicked -> Unit
|
||||
}
|
||||
}
|
||||
|
||||
private fun register() {
|
||||
val s = _uiState.value
|
||||
val nameError = if (s.name.isBlank()) {
|
||||
appContext.getString(R.string.error_register_name_required)
|
||||
} else {
|
||||
null
|
||||
}
|
||||
val emailTrimmed = s.email.trim()
|
||||
val emailError = when {
|
||||
emailTrimmed.isBlank() -> appContext.getString(R.string.error_register_email_required)
|
||||
!Patterns.EMAIL_ADDRESS.matcher(emailTrimmed).matches() -> {
|
||||
appContext.getString(R.string.error_register_email_invalid)
|
||||
}
|
||||
else -> null
|
||||
}
|
||||
val passwordError = when {
|
||||
s.password.length < MinPasswordLength -> {
|
||||
appContext.getString(R.string.error_register_password_short)
|
||||
}
|
||||
else -> null
|
||||
}
|
||||
val passwordConfirmError = when {
|
||||
s.password != s.passwordConfirm -> {
|
||||
appContext.getString(R.string.error_register_password_mismatch)
|
||||
}
|
||||
else -> null
|
||||
}
|
||||
if (nameError != null || emailError != null || passwordError != null || passwordConfirmError != null) {
|
||||
_uiState.value = s.copy(
|
||||
nameError = nameError,
|
||||
emailError = emailError,
|
||||
passwordError = passwordError,
|
||||
passwordConfirmError = passwordConfirmError
|
||||
)
|
||||
return
|
||||
}
|
||||
|
||||
viewModelScope.launch {
|
||||
_uiState.value = _uiState.value.copy(
|
||||
isLoading = true,
|
||||
nameError = null,
|
||||
emailError = null,
|
||||
passwordError = null,
|
||||
passwordConfirmError = null
|
||||
)
|
||||
try {
|
||||
val result = authService.registerWithEmail(
|
||||
email = emailTrimmed,
|
||||
password = s.password,
|
||||
name = s.name.trim()
|
||||
)
|
||||
if (result.isSuccess) {
|
||||
_actions.tryEmit(Action.OpenManage)
|
||||
} else {
|
||||
val message = result.exceptionOrNull()?.message
|
||||
?: appContext.getString(R.string.toast_email_auth_failed)
|
||||
_actions.tryEmit(Action.ShowMessage(message))
|
||||
}
|
||||
} finally {
|
||||
_uiState.value = _uiState.value.copy(isLoading = false)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -43,6 +43,7 @@ import org.db3.airmq.features.entry.SplashScreen
|
||||
import org.db3.airmq.features.entry.WizardScreen
|
||||
import org.db3.airmq.features.location.LocationScreen
|
||||
import org.db3.airmq.features.login.EmailLoginScreen
|
||||
import org.db3.airmq.features.login.EmailRegisterScreen
|
||||
import org.db3.airmq.features.login.LoginScreen
|
||||
import org.db3.airmq.features.manage.ManageScreen
|
||||
import org.db3.airmq.features.map.MapScreen
|
||||
@@ -234,6 +235,17 @@ fun AirMQNavGraph(modifier: Modifier = Modifier) {
|
||||
popUpTo(AirMqRoutes.LOGIN) { inclusive = true }
|
||||
}
|
||||
},
|
||||
onOpenRegister = { navController.navigate(AirMqRoutes.EMAIL_REGISTER) },
|
||||
onBack = { navController.popBackStack() }
|
||||
)
|
||||
}
|
||||
composable(AirMqRoutes.EMAIL_REGISTER) {
|
||||
EmailRegisterScreen(
|
||||
onRegisterSuccess = {
|
||||
navController.navigate(AirMqRoutes.MANAGE) {
|
||||
popUpTo(AirMqRoutes.LOGIN) { inclusive = true }
|
||||
}
|
||||
},
|
||||
onBack = { navController.popBackStack() }
|
||||
)
|
||||
}
|
||||
|
||||
@@ -13,6 +13,7 @@ object AirMqRoutes {
|
||||
const val SETUP = "detail/setup"
|
||||
const val LOGIN = "detail/login"
|
||||
const val EMAIL_LOGIN = "detail/email-login"
|
||||
const val EMAIL_REGISTER = "detail/email-register"
|
||||
const val NEWS = "detail/news"
|
||||
const val NEWS_DETAIL = "detail/news/{newsId}"
|
||||
const val DEVICE = "detail/device/{deviceId}"
|
||||
|
||||
@@ -93,6 +93,7 @@
|
||||
<string name="button_connecting">Connecting…</string>
|
||||
|
||||
<string name="text_sign_in_email">Sign in with email</string>
|
||||
<string name="text_register_email_title">Create account</string>
|
||||
|
||||
<!-- Hints -->
|
||||
<string name="hint_email">Email</string>
|
||||
@@ -101,6 +102,14 @@
|
||||
<string name="hint_setup_name">Name</string>
|
||||
<string name="hint_setup_model">Model</string>
|
||||
<string name="hint_setup_wifi">WiFi network</string>
|
||||
<string name="hint_register_name">Your name</string>
|
||||
<string name="hint_password_confirm">Confirm password</string>
|
||||
|
||||
<string name="error_register_name_required">Enter your name</string>
|
||||
<string name="error_register_email_required">Enter your email</string>
|
||||
<string name="error_register_email_invalid">Enter a valid email address</string>
|
||||
<string name="error_register_password_short">Password must be at least 6 characters</string>
|
||||
<string name="error_register_password_mismatch">Passwords do not match</string>
|
||||
|
||||
<!-- Content descriptors -->
|
||||
<string name="content_desc_user_pic">User Picture</string>
|
||||
|
||||
Reference in New Issue
Block a user