Refine Android app into focused mobile signer flow
This commit is contained in:
@@ -161,8 +161,8 @@ private val CardGlow = Brush.linearGradient(
|
|||||||
|
|
||||||
private const val SignatureHeight = 60
|
private const val SignatureHeight = 60
|
||||||
|
|
||||||
private enum class AuthMode { Login, Register, VerifyNotice }
|
private enum class AuthMode { Login }
|
||||||
private enum class AppTab(val title: String) { Dashboard("Inicio"), Documents("Documentos"), Sign("Firmar"), Certificates("Certificados"), Profile("Perfil") }
|
private enum class AppTab(val title: String) { Documents("Documentos"), Sign("Firmar"), Certificates("Certificados") }
|
||||||
|
|
||||||
private data class PdfPreviewState(
|
private data class PdfPreviewState(
|
||||||
val file: File,
|
val file: File,
|
||||||
@@ -200,7 +200,7 @@ fun LinkedSignApp(viewModel: MainViewModel, appContext: Context, activity: Compo
|
|||||||
val state = viewModel.uiState
|
val state = viewModel.uiState
|
||||||
|
|
||||||
var authMode by rememberSaveable { mutableStateOf(AuthMode.Login) }
|
var authMode by rememberSaveable { mutableStateOf(AuthMode.Login) }
|
||||||
var selectedTab by rememberSaveable { mutableStateOf(AppTab.Dashboard) }
|
var selectedTab by rememberSaveable { mutableStateOf(AppTab.Documents) }
|
||||||
var pendingSignDocumentId by rememberSaveable { mutableStateOf<String?>(null) }
|
var pendingSignDocumentId by rememberSaveable { mutableStateOf<String?>(null) }
|
||||||
val biometricAvailable = remember(activity) { canUseBiometricAccess(activity) }
|
val biometricAvailable = remember(activity) { canUseBiometricAccess(activity) }
|
||||||
var appUnlocked by rememberSaveable { mutableStateOf(false) }
|
var appUnlocked by rememberSaveable { mutableStateOf(false) }
|
||||||
@@ -213,12 +213,6 @@ fun LinkedSignApp(viewModel: MainViewModel, appContext: Context, activity: Compo
|
|||||||
viewModel.bootstrap()
|
viewModel.bootstrap()
|
||||||
}
|
}
|
||||||
|
|
||||||
LaunchedEffect(state.emailForVerificationNotice) {
|
|
||||||
if (!state.emailForVerificationNotice.isNullOrBlank()) {
|
|
||||||
authMode = AuthMode.VerifyNotice
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
LaunchedEffect(state.loading, state.authenticated, biometricAvailable) {
|
LaunchedEffect(state.loading, state.authenticated, biometricAvailable) {
|
||||||
if (!state.loading && !bootstrapGateHandled) {
|
if (!state.loading && !bootstrapGateHandled) {
|
||||||
bootstrapGateHandled = true
|
bootstrapGateHandled = true
|
||||||
@@ -252,6 +246,19 @@ fun LinkedSignApp(viewModel: MainViewModel, appContext: Context, activity: Compo
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
actions = {
|
||||||
|
IconButton(onClick = {
|
||||||
|
scope.launch {
|
||||||
|
viewModel.logout()
|
||||||
|
authMode = AuthMode.Login
|
||||||
|
pendingSignDocumentId = null
|
||||||
|
appUnlocked = !(biometricAvailable && state.biometricEnabled && state.biometricHasCredentials)
|
||||||
|
snackbarHostState.showSnackbar("Sesión cerrada.")
|
||||||
|
}
|
||||||
|
}) {
|
||||||
|
Icon(Icons.Outlined.Logout, contentDescription = "Cerrar sesión", tint = Color.White)
|
||||||
|
}
|
||||||
|
},
|
||||||
colors = TopAppBarDefaults.topAppBarColors(
|
colors = TopAppBarDefaults.topAppBarColors(
|
||||||
containerColor = Brand,
|
containerColor = Brand,
|
||||||
titleContentColor = Color.White
|
titleContentColor = Color.White
|
||||||
@@ -266,11 +273,9 @@ fun LinkedSignApp(viewModel: MainViewModel, appContext: Context, activity: Compo
|
|||||||
tonalElevation = 0.dp
|
tonalElevation = 0.dp
|
||||||
) {
|
) {
|
||||||
val tabs = listOf(
|
val tabs = listOf(
|
||||||
AppTab.Dashboard to Icons.Outlined.Dashboard,
|
|
||||||
AppTab.Documents to Icons.Outlined.Description,
|
AppTab.Documents to Icons.Outlined.Description,
|
||||||
AppTab.Sign to Icons.Outlined.Draw,
|
AppTab.Sign to Icons.Outlined.Draw,
|
||||||
AppTab.Certificates to Icons.Outlined.Badge,
|
AppTab.Certificates to Icons.Outlined.Badge
|
||||||
AppTab.Profile to Icons.Outlined.AccountCircle
|
|
||||||
)
|
)
|
||||||
tabs.forEach { (tab, icon) ->
|
tabs.forEach { (tab, icon) ->
|
||||||
NavigationBarItem(
|
NavigationBarItem(
|
||||||
@@ -308,7 +313,7 @@ fun LinkedSignApp(viewModel: MainViewModel, appContext: Context, activity: Compo
|
|||||||
scope.launch {
|
scope.launch {
|
||||||
runCatching { viewModel.biometricLogin() }
|
runCatching { viewModel.biometricLogin() }
|
||||||
.onSuccess {
|
.onSuccess {
|
||||||
selectedTab = AppTab.Dashboard
|
selectedTab = AppTab.Documents
|
||||||
appUnlocked = true
|
appUnlocked = true
|
||||||
snackbarHostState.showSnackbar("Sesión iniciada con huella.")
|
snackbarHostState.showSnackbar("Sesión iniciada con huella.")
|
||||||
}
|
}
|
||||||
@@ -343,15 +348,11 @@ fun LinkedSignApp(viewModel: MainViewModel, appContext: Context, activity: Compo
|
|||||||
}
|
}
|
||||||
)
|
)
|
||||||
!state.authenticated -> AuthFlow(
|
!state.authenticated -> AuthFlow(
|
||||||
mode = authMode,
|
|
||||||
onModeChange = { authMode = it },
|
|
||||||
verifyEmail = state.emailForVerificationNotice,
|
|
||||||
onConsumeVerifyNotice = { viewModel.clearVerificationNotice() },
|
|
||||||
onLogin = { email, password ->
|
onLogin = { email, password ->
|
||||||
scope.launch {
|
scope.launch {
|
||||||
runCatching { viewModel.login(email, password) }
|
runCatching { viewModel.login(email, password) }
|
||||||
.onSuccess {
|
.onSuccess {
|
||||||
selectedTab = AppTab.Dashboard
|
selectedTab = AppTab.Documents
|
||||||
appUnlocked = true
|
appUnlocked = true
|
||||||
pendingBiometricEmail = email.trim()
|
pendingBiometricEmail = email.trim()
|
||||||
pendingBiometricPassword = password
|
pendingBiometricPassword = password
|
||||||
@@ -368,17 +369,6 @@ fun LinkedSignApp(viewModel: MainViewModel, appContext: Context, activity: Compo
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
onRegister = { email, password, certPassword, certificateUri, certificateName ->
|
|
||||||
scope.launch {
|
|
||||||
runCatching { viewModel.register(email, password, certPassword, certificateUri, certificateName) }
|
|
||||||
.onSuccess {
|
|
||||||
authMode = AuthMode.VerifyNotice
|
|
||||||
}
|
|
||||||
.onFailure { error ->
|
|
||||||
snackbarHostState.showSnackbar((error as? AppException)?.message ?: "No se pudo registrar.")
|
|
||||||
}
|
|
||||||
}
|
|
||||||
},
|
|
||||||
modifier = Modifier.padding(padding)
|
modifier = Modifier.padding(padding)
|
||||||
)
|
)
|
||||||
else -> AppFlow(
|
else -> AppFlow(
|
||||||
@@ -434,22 +424,6 @@ fun LinkedSignApp(viewModel: MainViewModel, appContext: Context, activity: Compo
|
|||||||
.onFailure { snackbarHostState.showSnackbar((it as? AppException)?.message ?: "No se pudo firmar el documento.") }
|
.onFailure { snackbarHostState.showSnackbar((it as? AppException)?.message ?: "No se pudo firmar el documento.") }
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
onRequestPasswordChange = { currentPassword, newPassword ->
|
|
||||||
scope.launch {
|
|
||||||
runCatching { viewModel.requestPasswordChange(currentPassword, newPassword) }
|
|
||||||
.onSuccess { snackbarHostState.showSnackbar("Te enviamos un correo para confirmar el cambio de contraseña.") }
|
|
||||||
.onFailure { snackbarHostState.showSnackbar((it as? AppException)?.message ?: "No se pudo solicitar el cambio.") }
|
|
||||||
}
|
|
||||||
},
|
|
||||||
onLogout = {
|
|
||||||
scope.launch {
|
|
||||||
viewModel.logout()
|
|
||||||
authMode = AuthMode.Login
|
|
||||||
pendingSignDocumentId = null
|
|
||||||
appUnlocked = !(biometricAvailable && state.biometricEnabled && state.biometricHasCredentials)
|
|
||||||
snackbarHostState.showSnackbar("Sesión cerrada.")
|
|
||||||
}
|
|
||||||
},
|
|
||||||
selectedSignDocumentId = pendingSignDocumentId,
|
selectedSignDocumentId = pendingSignDocumentId,
|
||||||
onSelectDocumentToSign = { documentId ->
|
onSelectDocumentToSign = { documentId ->
|
||||||
pendingSignDocumentId = documentId.toString()
|
pendingSignDocumentId = documentId.toString()
|
||||||
@@ -524,12 +498,7 @@ private fun LoadingState() {
|
|||||||
|
|
||||||
@Composable
|
@Composable
|
||||||
private fun AuthFlow(
|
private fun AuthFlow(
|
||||||
mode: AuthMode,
|
|
||||||
onModeChange: (AuthMode) -> Unit,
|
|
||||||
verifyEmail: String?,
|
|
||||||
onConsumeVerifyNotice: () -> Unit,
|
|
||||||
onLogin: (String, String) -> Unit,
|
onLogin: (String, String) -> Unit,
|
||||||
onRegister: (String, String, String, Uri, String) -> Unit,
|
|
||||||
modifier: Modifier = Modifier
|
modifier: Modifier = Modifier
|
||||||
) {
|
) {
|
||||||
Column(
|
Column(
|
||||||
@@ -554,7 +523,7 @@ private fun AuthFlow(
|
|||||||
}
|
}
|
||||||
Text("LinkedSign", color = Color.White, fontSize = AppFontSizes.brand, fontWeight = FontWeight.Bold)
|
Text("LinkedSign", color = Color.White, fontSize = AppFontSizes.brand, fontWeight = FontWeight.Bold)
|
||||||
Text(
|
Text(
|
||||||
if (mode == AuthMode.Register) "Crear cuenta" else "Firma digital profesional",
|
"Firmador móvil",
|
||||||
color = Color.White.copy(alpha = 0.75f),
|
color = Color.White.copy(alpha = 0.75f),
|
||||||
fontSize = AppFontSizes.base
|
fontSize = AppFontSizes.base
|
||||||
)
|
)
|
||||||
@@ -569,23 +538,7 @@ private fun AuthFlow(
|
|||||||
.padding(horizontal = 20.dp, vertical = 24.dp),
|
.padding(horizontal = 20.dp, vertical = 24.dp),
|
||||||
verticalArrangement = Arrangement.spacedBy(16.dp)
|
verticalArrangement = Arrangement.spacedBy(16.dp)
|
||||||
) {
|
) {
|
||||||
when (mode) {
|
LoginCard(onSubmit = onLogin)
|
||||||
AuthMode.Login -> LoginCard(
|
|
||||||
onSubmit = onLogin,
|
|
||||||
onGoRegister = { onModeChange(AuthMode.Register) }
|
|
||||||
)
|
|
||||||
AuthMode.Register -> RegisterCard(
|
|
||||||
onSubmit = onRegister,
|
|
||||||
onGoLogin = { onModeChange(AuthMode.Login) }
|
|
||||||
)
|
|
||||||
AuthMode.VerifyNotice -> VerifyNoticeCard(
|
|
||||||
email = verifyEmail,
|
|
||||||
onBackToLogin = {
|
|
||||||
onConsumeVerifyNotice()
|
|
||||||
onModeChange(AuthMode.Login)
|
|
||||||
}
|
|
||||||
)
|
|
||||||
}
|
|
||||||
if (BuildConfig.DEBUG) {
|
if (BuildConfig.DEBUG) {
|
||||||
Text(
|
Text(
|
||||||
"Servidor: ${BuildConfig.API_BASE_URL}",
|
"Servidor: ${BuildConfig.API_BASE_URL}",
|
||||||
@@ -682,8 +635,6 @@ private fun AppFlow(
|
|||||||
onUploadCertificate: (Uri, String, String, String) -> Unit,
|
onUploadCertificate: (Uri, String, String, String) -> Unit,
|
||||||
onDeleteDocument: (UnifiedDocumentItem) -> Unit,
|
onDeleteDocument: (UnifiedDocumentItem) -> Unit,
|
||||||
onSign: (Int, Int, String, String, String, Int, SignatureRect?) -> Unit,
|
onSign: (Int, Int, String, String, String, Int, SignatureRect?) -> Unit,
|
||||||
onRequestPasswordChange: (String, String) -> Unit,
|
|
||||||
onLogout: () -> Unit,
|
|
||||||
onSelectDocumentToSign: (Int) -> Unit,
|
onSelectDocumentToSign: (Int) -> Unit,
|
||||||
accessToken: () -> String?
|
accessToken: () -> String?
|
||||||
) {
|
) {
|
||||||
@@ -693,7 +644,6 @@ private fun AppFlow(
|
|||||||
.padding(padding)
|
.padding(padding)
|
||||||
) {
|
) {
|
||||||
when (selectedTab) {
|
when (selectedTab) {
|
||||||
AppTab.Dashboard -> DashboardScreen(state = state, onSync = onSync)
|
|
||||||
AppTab.Documents -> DocumentsScreen(
|
AppTab.Documents -> DocumentsScreen(
|
||||||
state = state,
|
state = state,
|
||||||
accessToken = accessToken,
|
accessToken = accessToken,
|
||||||
@@ -711,13 +661,12 @@ private fun AppFlow(
|
|||||||
onSign = onSign
|
onSign = onSign
|
||||||
)
|
)
|
||||||
AppTab.Certificates -> CertificatesScreen(state = state, onSync = onSync, onUploadCertificate = onUploadCertificate)
|
AppTab.Certificates -> CertificatesScreen(state = state, onSync = onSync, onUploadCertificate = onUploadCertificate)
|
||||||
AppTab.Profile -> ProfileScreen(state = state, onRequestPasswordChange = onRequestPasswordChange, onLogout = onLogout, onSync = onSync)
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@Composable
|
@Composable
|
||||||
private fun LoginCard(onSubmit: (String, String) -> Unit, onGoRegister: () -> Unit) {
|
private fun LoginCard(onSubmit: (String, String) -> Unit) {
|
||||||
var email by rememberSaveable { mutableStateOf("") }
|
var email by rememberSaveable { mutableStateOf("") }
|
||||||
var password by rememberSaveable { mutableStateOf("") }
|
var password by rememberSaveable { mutableStateOf("") }
|
||||||
|
|
||||||
@@ -750,7 +699,7 @@ private fun LoginCard(onSubmit: (String, String) -> Unit, onGoRegister: () -> Un
|
|||||||
}
|
}
|
||||||
Column(verticalArrangement = Arrangement.spacedBy(4.dp)) {
|
Column(verticalArrangement = Arrangement.spacedBy(4.dp)) {
|
||||||
Text("Iniciar sesión", style = MaterialTheme.typography.titleLarge, fontWeight = FontWeight.Bold, color = BrandDark)
|
Text("Iniciar sesión", style = MaterialTheme.typography.titleLarge, fontWeight = FontWeight.Bold, color = BrandDark)
|
||||||
Text("Accede a tu cuenta LinkedSign", color = TextMuted, style = MaterialTheme.typography.bodySmall)
|
Text("Accede con tu cuenta existente. El registro se realiza desde la web.", color = TextMuted, style = MaterialTheme.typography.bodySmall)
|
||||||
}
|
}
|
||||||
AppTextField(value = email, onValueChange = { email = it }, label = "Correo electrónico", keyboardType = KeyboardType.Email)
|
AppTextField(value = email, onValueChange = { email = it }, label = "Correo electrónico", keyboardType = KeyboardType.Email)
|
||||||
AppTextField(value = password, onValueChange = { password = it }, label = "Contraseña", isPassword = true)
|
AppTextField(value = password, onValueChange = { password = it }, label = "Contraseña", isPassword = true)
|
||||||
@@ -765,180 +714,17 @@ private fun LoginCard(onSubmit: (String, String) -> Unit, onGoRegister: () -> Un
|
|||||||
Spacer(Modifier.width(8.dp))
|
Spacer(Modifier.width(8.dp))
|
||||||
Text("Entrar", fontWeight = FontWeight.SemiBold, fontSize = AppFontSizes.xl)
|
Text("Entrar", fontWeight = FontWeight.SemiBold, fontSize = AppFontSizes.xl)
|
||||||
}
|
}
|
||||||
Row(
|
|
||||||
modifier = Modifier.fillMaxWidth(),
|
|
||||||
horizontalArrangement = Arrangement.Center,
|
|
||||||
verticalAlignment = Alignment.CenterVertically
|
|
||||||
) {
|
|
||||||
Text("¿No tienes cuenta?", color = Color(0xFF6B7280), fontSize = AppFontSizes.base)
|
|
||||||
TextButton(onClick = onGoRegister, contentPadding = PaddingValues(horizontal = 6.dp)) {
|
|
||||||
Text("Regístrate", color = Brand, fontWeight = FontWeight.SemiBold, fontSize = AppFontSizes.base)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@Composable
|
|
||||||
private fun RegisterCard(onSubmit: (String, String, String, Uri, String) -> Unit, onGoLogin: () -> Unit) {
|
|
||||||
val context = LocalContext.current
|
|
||||||
var email by rememberSaveable { mutableStateOf("") }
|
|
||||||
var password by rememberSaveable { mutableStateOf("") }
|
|
||||||
var confirmPassword by rememberSaveable { mutableStateOf("") }
|
|
||||||
var certPassword by rememberSaveable { mutableStateOf("") }
|
|
||||||
var consentAccepted by rememberSaveable { mutableStateOf(false) }
|
|
||||||
var certificateUri by rememberSaveable { mutableStateOf<Uri?>(null) }
|
|
||||||
var certificateName by rememberSaveable { mutableStateOf("") }
|
|
||||||
val passwordProblems = passwordIssues(password)
|
|
||||||
val launcher = rememberLauncherForActivityResult(ActivityResultContracts.OpenDocument()) { uri ->
|
|
||||||
uri ?: return@rememberLauncherForActivityResult
|
|
||||||
try {
|
|
||||||
context.contentResolver.takePersistableUriPermission(uri, Intent.FLAG_GRANT_READ_URI_PERMISSION)
|
|
||||||
} catch (_: Exception) {
|
|
||||||
}
|
|
||||||
certificateUri = uri
|
|
||||||
certificateName = queryFileName(context, uri) ?: "certificado.p12"
|
|
||||||
}
|
|
||||||
|
|
||||||
ElevatedCard(
|
|
||||||
shape = RoundedCornerShape(24.dp),
|
|
||||||
elevation = CardDefaults.elevatedCardElevation(defaultElevation = 8.dp),
|
|
||||||
colors = CardDefaults.elevatedCardColors(containerColor = Color.White)
|
|
||||||
) {
|
|
||||||
Column(modifier = Modifier.padding(24.dp), verticalArrangement = Arrangement.spacedBy(14.dp)) {
|
|
||||||
Surface(
|
Surface(
|
||||||
color = SurfaceAlt,
|
color = SurfaceAlt,
|
||||||
shape = RoundedCornerShape(16.dp),
|
shape = RoundedCornerShape(12.dp),
|
||||||
border = BorderStroke(1.dp, BrandLight)
|
border = BorderStroke(1.dp, StrokeSoft)
|
||||||
) {
|
) {
|
||||||
Row(
|
Text(
|
||||||
modifier = Modifier.padding(horizontal = 12.dp, vertical = 10.dp),
|
"La app móvil se usa para firmar y revisar documentos. Para crear cuenta o gestionar accesos, usa la versión web.",
|
||||||
verticalAlignment = Alignment.CenterVertically,
|
color = TextMuted,
|
||||||
horizontalArrangement = Arrangement.spacedBy(10.dp)
|
fontSize = AppFontSizes.sm,
|
||||||
) {
|
modifier = Modifier.padding(12.dp)
|
||||||
Box(
|
)
|
||||||
modifier = Modifier.size(32.dp).background(Color.White, RoundedCornerShape(12.dp)),
|
|
||||||
contentAlignment = Alignment.Center
|
|
||||||
) {
|
|
||||||
Icon(Icons.Outlined.Verified, contentDescription = null, tint = Brand, modifier = Modifier.size(18.dp))
|
|
||||||
}
|
|
||||||
Column {
|
|
||||||
Text("Alta protegida", color = BrandDark, fontWeight = FontWeight.SemiBold, fontSize = AppFontSizes.base)
|
|
||||||
Text("Correo válido, clave fuerte y certificado para el consentimiento legal", color = TextMuted, fontSize = AppFontSizes.sm)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
Column(verticalArrangement = Arrangement.spacedBy(4.dp)) {
|
|
||||||
Text("Crear cuenta", style = MaterialTheme.typography.titleLarge, fontWeight = FontWeight.Bold, color = BrandDark)
|
|
||||||
Text("Registra tu cuenta en LinkedSign", color = TextMuted, style = MaterialTheme.typography.bodySmall)
|
|
||||||
}
|
|
||||||
AppTextField(value = email, onValueChange = { email = it }, label = "Correo electrónico", keyboardType = KeyboardType.Email)
|
|
||||||
AppTextField(value = password, onValueChange = { password = it }, label = "Contraseña", isPassword = true)
|
|
||||||
PasswordChecklist(password = password)
|
|
||||||
AppTextField(value = confirmPassword, onValueChange = { confirmPassword = it }, label = "Confirmar contraseña", isPassword = true)
|
|
||||||
AppTextField(value = certPassword, onValueChange = { certPassword = it }, label = "Contraseña del certificado", isPassword = true)
|
|
||||||
Surface(color = SurfaceAlt, shape = RoundedCornerShape(14.dp), border = BorderStroke(1.dp, StrokeSoft)) {
|
|
||||||
Column(modifier = Modifier.padding(14.dp), verticalArrangement = Arrangement.spacedBy(10.dp)) {
|
|
||||||
Text("Registro legal inicial", color = BrandDark, fontWeight = FontWeight.SemiBold, fontSize = AppFontSizes.base)
|
|
||||||
Text(
|
|
||||||
"Debes cargar tu P12/PFX. La contraseña del certificado solo se usará para firmar los documentos legales del primer registro y no se guardará.",
|
|
||||||
color = TextMuted,
|
|
||||||
fontSize = AppFontSizes.sm
|
|
||||||
)
|
|
||||||
Row(horizontalArrangement = Arrangement.spacedBy(10.dp), verticalAlignment = Alignment.CenterVertically) {
|
|
||||||
OutlinedActionButton(
|
|
||||||
label = if (certificateName.isBlank()) "Seleccionar P12/PFX" else certificateName,
|
|
||||||
icon = Icons.Outlined.Badge,
|
|
||||||
modifier = Modifier.weight(1f)
|
|
||||||
) {
|
|
||||||
launcher.launch(arrayOf("*/*"))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
Row(verticalAlignment = Alignment.Top, horizontalArrangement = Arrangement.spacedBy(10.dp)) {
|
|
||||||
androidx.compose.material3.Checkbox(
|
|
||||||
checked = consentAccepted,
|
|
||||||
onCheckedChange = { consentAccepted = it }
|
|
||||||
)
|
|
||||||
Text(
|
|
||||||
"Acepto los términos de uso, la política de privacidad y el consentimiento expreso para tratamiento de datos y uso de firma electrónica.",
|
|
||||||
color = TextMuted,
|
|
||||||
fontSize = AppFontSizes.sm,
|
|
||||||
modifier = Modifier.weight(1f)
|
|
||||||
)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if (passwordProblems.isNotEmpty() && password.isNotBlank()) {
|
|
||||||
Surface(color = WarningBg, shape = RoundedCornerShape(10.dp)) {
|
|
||||||
Text("La contraseña aún no cumple todos los requisitos.", color = Warning, modifier = Modifier.padding(10.dp), fontSize = AppFontSizes.md)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if (password != confirmPassword && confirmPassword.isNotBlank()) {
|
|
||||||
Surface(color = Color(0xFFFEE2E2), shape = RoundedCornerShape(10.dp)) {
|
|
||||||
Text("Las contraseñas no coinciden.", color = Danger, modifier = Modifier.padding(10.dp), fontSize = AppFontSizes.md)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
Spacer(Modifier.height(2.dp))
|
|
||||||
Button(
|
|
||||||
onClick = {
|
|
||||||
when {
|
|
||||||
passwordProblems.isNotEmpty() -> Unit
|
|
||||||
password != confirmPassword -> Unit
|
|
||||||
certPassword.isBlank() -> Unit
|
|
||||||
certificateUri == null -> Unit
|
|
||||||
!consentAccepted -> Unit
|
|
||||||
else -> onSubmit(email, password, certPassword, certificateUri!!, certificateName.ifBlank { "certificado.p12" })
|
|
||||||
}
|
|
||||||
},
|
|
||||||
modifier = Modifier.fillMaxWidth().height(50.dp),
|
|
||||||
shape = RoundedCornerShape(14.dp),
|
|
||||||
colors = ButtonDefaults.buttonColors(containerColor = Brand)
|
|
||||||
) {
|
|
||||||
Text("Crear cuenta", fontWeight = FontWeight.SemiBold, fontSize = AppFontSizes.xl)
|
|
||||||
}
|
|
||||||
Row(
|
|
||||||
modifier = Modifier.fillMaxWidth(),
|
|
||||||
horizontalArrangement = Arrangement.Center,
|
|
||||||
verticalAlignment = Alignment.CenterVertically
|
|
||||||
) {
|
|
||||||
Text("¿Ya tienes cuenta?", color = Color(0xFF6B7280), fontSize = AppFontSizes.base)
|
|
||||||
TextButton(onClick = onGoLogin, contentPadding = PaddingValues(horizontal = 6.dp)) {
|
|
||||||
Text("Inicia sesión", color = Brand, fontWeight = FontWeight.SemiBold, fontSize = AppFontSizes.base)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@Composable
|
|
||||||
private fun VerifyNoticeCard(email: String?, onBackToLogin: () -> Unit) {
|
|
||||||
ElevatedCard(
|
|
||||||
shape = RoundedCornerShape(24.dp),
|
|
||||||
elevation = CardDefaults.elevatedCardElevation(defaultElevation = 8.dp),
|
|
||||||
colors = CardDefaults.elevatedCardColors(containerColor = Color.White)
|
|
||||||
) {
|
|
||||||
Column(modifier = Modifier.padding(24.dp), verticalArrangement = Arrangement.spacedBy(16.dp), horizontalAlignment = Alignment.CenterHorizontally) {
|
|
||||||
Box(
|
|
||||||
modifier = Modifier
|
|
||||||
.size(64.dp)
|
|
||||||
.background(Brush.radialGradient(listOf(SuccessBg, Color.White)), CircleShape),
|
|
||||||
contentAlignment = Alignment.Center
|
|
||||||
) {
|
|
||||||
Icon(Icons.Outlined.CheckCircle, contentDescription = null, tint = Success, modifier = Modifier.size(32.dp))
|
|
||||||
}
|
|
||||||
Text("Confirma tu cuenta", style = MaterialTheme.typography.titleLarge, fontWeight = FontWeight.Bold, color = BrandDark)
|
|
||||||
Text(
|
|
||||||
"Enviamos un enlace de verificación a ${email ?: "tu correo"}. Debes confirmarlo antes de iniciar sesión.",
|
|
||||||
color = TextMuted,
|
|
||||||
style = MaterialTheme.typography.bodyMedium
|
|
||||||
)
|
|
||||||
Button(
|
|
||||||
onClick = onBackToLogin,
|
|
||||||
modifier = Modifier.fillMaxWidth().height(50.dp),
|
|
||||||
shape = RoundedCornerShape(14.dp),
|
|
||||||
colors = ButtonDefaults.buttonColors(containerColor = Brand)
|
|
||||||
) {
|
|
||||||
Text("Volver al inicio", fontWeight = FontWeight.SemiBold)
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user