Skip to content

Commit

Permalink
Credit Card Form
Browse files Browse the repository at this point in the history
  • Loading branch information
SAUL committed Oct 28, 2024
1 parent 9514953 commit 77833fa
Show file tree
Hide file tree
Showing 4 changed files with 268 additions and 5 deletions.
22 changes: 22 additions & 0 deletions src/main/kotlin/core/form/validation/CommonRules.kt
Original file line number Diff line number Diff line change
Expand Up @@ -39,6 +39,28 @@ val passwordRule: ValidationRule = ValidationRule(
errorMessage = "Password must be 8+ chars, with a number, symbol, upper & lower case."
)

val creditCardRule: ValidationRule = ValidationRule(
condition = { card ->
card.matches(Regex("^4[0-9]{12}(?:[0-9]{3})?$"))
|| card.matches(Regex("^5[1-5][0-9]{14}$"))
},
errorMessage = "Invalid Credit Card Number"
)

val creditCardPinRule: ValidationRule = ValidationRule(
condition = { card ->
card.matches(Regex("^[0-9]{3,4}$"))
},
errorMessage = "Invalid Credit Card PIN"
)

val creditCardExpiryDateRule: ValidationRule = ValidationRule(
condition = { card ->
card.matches(Regex("^(0[1-9]|1[0-2])/([0-9]{2})$"))
},
errorMessage = "Invalid Credit Card Expiry Date"
)

fun lengthRule(field: String, length: Int): ValidationRule {
return ValidationRule(
condition = { field.length >= length },
Expand Down
54 changes: 54 additions & 0 deletions src/main/kotlin/ui/components/FormComponents.kt
Original file line number Diff line number Diff line change
Expand Up @@ -102,6 +102,60 @@ fun FormTextField(
}
}

@OptIn(ExperimentalMaterial3Api::class)
@Composable
fun FormTextArea(
value: String,
onValueChange: (String) -> Unit,
modifier: Modifier = Modifier,
label: String = "",
minLines: Int = 3,
maxLines: Int = 5
) {
val interactionSource = remember { MutableInteractionSource() }

Surface(
modifier = modifier.height(IntrinsicSize.Min),
color = secondary,
shape = RoundedCornerShape(8.dp),
) {
Column(
modifier = Modifier
.fillMaxWidth()
.padding(horizontal = 14.dp)
) {
if (label.isNotEmpty()) {
Text(
text = label,
fontSize = 10.sp,
fontFamily = Font.RussoOne,
color = Color.White,
modifier = Modifier.padding(top = 8.dp, bottom = 4.dp)
)
}

BasicTextField(
value = value,
onValueChange = onValueChange,
interactionSource = interactionSource,
modifier = Modifier
.fillMaxWidth()
.padding(bottom = 8.dp),
textStyle = TextStyle(
fontFamily = Font.RussoOne,
color = Color.White,
textAlign = TextAlign.Start,
fontSize = 12.sp,
lineHeight = 18.sp
),
minLines = minLines,
maxLines = maxLines,
cursorBrush = SolidColor(Color.White),
)
}
}
}

@OptIn(ExperimentalMaterial3Api::class)
@Composable
fun PasswordTextField(
Expand Down
150 changes: 145 additions & 5 deletions src/main/kotlin/ui/components/forms/passwordmgnt/CreditCardForm.kt
Original file line number Diff line number Diff line change
@@ -1,27 +1,49 @@
package ui.components.forms.passwordmgnt

import androidx.compose.foundation.background
import androidx.compose.foundation.layout.Arrangement
import androidx.compose.foundation.layout.Column
import androidx.compose.foundation.layout.Row
import androidx.compose.foundation.layout.fillMaxSize
import androidx.compose.foundation.layout.*
import androidx.compose.material.icons.Icons
import androidx.compose.material.icons.filled.CreditCard
import androidx.compose.material.icons.filled.DateRange
import androidx.compose.material.icons.filled.Pin
import androidx.compose.material3.Text
import androidx.compose.runtime.Composable
import androidx.compose.runtime.getValue
import androidx.compose.runtime.remember
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.graphics.Color
import androidx.compose.ui.unit.dp
import androidx.compose.ui.unit.sp
import cafe.adriel.voyager.core.screen.Screen
import cafe.adriel.voyager.koin.koinScreenModel
import cafe.adriel.voyager.navigator.LocalNavigator
import ui.components.FormTextArea
import ui.components.FormTextField
import ui.theme.Font
import ui.theme.primary
import ui.theme.secondary
import ui.theme.tertiary
import ui.validators.CreditCardFormFieldName
import ui.validators.creditCardFormValidator
import viewmodel.PasswordMgntScreenModel

class CreditCardForm : Screen {

@Composable
override fun Content() {

val formValidator = remember { creditCardFormValidator() }
val isFormValid by formValidator.isValid
val screenModel = koinScreenModel<PasswordMgntScreenModel>()
val navigator = LocalNavigator.current

val cardNumber = formValidator.getField(CreditCardFormFieldName.CARD_NUMBER)
val cvc = formValidator.getField(CreditCardFormFieldName.CARD_CVC)
val pin = formValidator.getField(CreditCardFormFieldName.CARD_PIN)
val expiry = formValidator.getField(CreditCardFormFieldName.CARD_EXPIRY)
val notes = formValidator.getField(CreditCardFormFieldName.CARD_NOTES)

Column(
modifier = Modifier.fillMaxSize()
.background(secondary)
Expand All @@ -47,8 +69,126 @@ class CreditCardForm : Screen {
horizontalArrangement = Arrangement.Center,
verticalAlignment = Alignment.CenterVertically
) {
Column(
modifier = Modifier.background(primary)
.padding(PaddingValues(end = 20.dp)),
horizontalAlignment = Alignment.Start,
verticalArrangement = Arrangement.spacedBy(5.dp, Alignment.CenterVertically)
) {
Row(horizontalArrangement = Arrangement.spacedBy(25.dp)) {
Column(modifier = Modifier.height(80.dp)) {
FormTextField(
value = cardNumber?.value?.value ?: "",
onValueChange = { newValue ->
cardNumber?.value?.value = newValue
formValidator.validateField(CreditCardFormFieldName.CARD_NUMBER)
},
label = CreditCardFormFieldName.CARD_NUMBER.fieldName,
icon = Icons.Filled.CreditCard,
modifier = Modifier.height(45.dp).width(400.dp)
)
cardNumber?.error?.value?.let {
Text(
text = it,
color = Color.Red,
fontFamily = Font.RobotoRegular,
fontSize = 10.sp,
modifier = Modifier.padding(start = 8.dp)
)
}
}
Column(modifier = Modifier.height(80.dp)) {
FormTextField(
value = cvc?.value?.value ?: "",
onValueChange = { newValue ->
cvc?.value?.value = newValue
formValidator.validateField(CreditCardFormFieldName.CARD_CVC)
},
label = CreditCardFormFieldName.CARD_CVC.fieldName,
icon = Icons.Filled.Pin,
modifier = Modifier.height(45.dp).width(400.dp)
)
cvc?.error?.value?.let {
Text(
text = it,
color = Color.Red,
fontFamily = Font.RobotoRegular,
fontSize = 10.sp,
modifier = Modifier.padding(start = 8.dp)
)
}
}
}

Row(horizontalArrangement = Arrangement.spacedBy(25.dp)) {
Column(modifier = Modifier.height(80.dp)) {
FormTextField(
value = pin?.value?.value ?: "",
onValueChange = { newValue ->
pin?.value?.value = newValue
formValidator.validateField(CreditCardFormFieldName.CARD_PIN)
},
label = CreditCardFormFieldName.CARD_PIN.fieldName,
icon = Icons.Filled.Pin,
modifier = Modifier.height(45.dp).width(400.dp)
)
pin?.error?.value?.let {
Text(
text = it,
color = Color.Red,
fontFamily = Font.RobotoRegular,
fontSize = 10.sp,
modifier = Modifier.padding(start = 8.dp)
)
}
}
Column(modifier = Modifier.height(80.dp)) {
FormTextField(
value = expiry?.value?.value ?: "",
onValueChange = { newValue ->
expiry?.value?.value = newValue
formValidator.validateField(CreditCardFormFieldName.CARD_EXPIRY)
},
label = CreditCardFormFieldName.CARD_EXPIRY.fieldName,
icon = Icons.Filled.DateRange,
modifier = Modifier.height(45.dp).width(400.dp)
)
expiry?.error?.value?.let {
Text(
text = it,
color = Color.Red,
fontFamily = Font.RobotoRegular,
fontSize = 10.sp,
modifier = Modifier.padding(start = 8.dp)
)
}
}
}

Row(horizontalArrangement = Arrangement.spacedBy(25.dp)) {
Column(modifier = Modifier.height(120.dp)) {
FormTextArea(
value = notes?.value?.value ?: "",
onValueChange = { newValue ->
notes?.value?.value = newValue
formValidator.validateField(CreditCardFormFieldName.CARD_NOTES)
},
label = CreditCardFormFieldName.CARD_NOTES.fieldName,
modifier = Modifier.width(400.dp)
)
notes?.error?.value?.let {
Text(
text = it,
color = Color.Red,
fontFamily = Font.RobotoRegular,
fontSize = 10.sp,
modifier = Modifier.padding(start = 8.dp)
)
}
}
}

}
}

Row(
Expand All @@ -61,7 +201,7 @@ class CreditCardForm : Screen {
Footer(
{ },
{ navigator?.pop() },
true
isFormValid
)
}

Expand Down
47 changes: 47 additions & 0 deletions src/main/kotlin/ui/validators/CreditCardFormValidator.kt
Original file line number Diff line number Diff line change
@@ -0,0 +1,47 @@
package ui.validators

import core.form.FormField
import core.form.FormFieldName
import core.form.validation.*

fun creditCardFormValidator(): FormValidator {
return FormValidator()
.addField(
CreditCardFormFieldName.CARD_NUMBER, FormField(
validator = Validator().addRule(creditCardRule)
.addRule(notNullRule(CreditCardFormFieldName.CARD_NUMBER.fieldName))
)
)
.addField(
CreditCardFormFieldName.CARD_PIN, FormField(
validator = Validator().addRule(creditCardPinRule)
.addRule(notNullRule(CreditCardFormFieldName.CARD_PIN.fieldName))
)
)
.addField(
CreditCardFormFieldName.CARD_CVC, FormField(
validator = Validator().addRule(creditCardPinRule)
.addRule(notNullRule(CreditCardFormFieldName.CARD_CVC.fieldName))
)
)
.addField(
CreditCardFormFieldName.CARD_EXPIRY, FormField(
validator = Validator().addRule(notNullRule(CreditCardFormFieldName.CARD_EXPIRY.fieldName))
.addRule(creditCardExpiryDateRule)
)
)
.addField(
CreditCardFormFieldName.CARD_NOTES, FormField(
validator = Validator()
)
)
.validateAllFields()
}

enum class CreditCardFormFieldName(val fieldName: String) : FormFieldName {
CARD_NUMBER("Card number"),
CARD_PIN("Pin"),
CARD_CVC("Cvc"),
CARD_EXPIRY("Expiry Date"),
CARD_NOTES("Notes")
}

0 comments on commit 77833fa

Please sign in to comment.