Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Feature/autofill #188

Closed
wants to merge 3 commits into from
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -5,15 +5,19 @@ import androidx.compose.foundation.layout.Row
import androidx.compose.foundation.layout.wrapContentHeight
import androidx.compose.runtime.Composable
import androidx.compose.ui.Alignment
import androidx.compose.ui.ExperimentalComposeUiApi
import androidx.compose.ui.Modifier
import androidx.compose.ui.autofill.AutofillType
import com.checkout.frames.style.view.InputComponentViewStyle
import com.checkout.frames.view.InputField
import com.checkout.frames.view.TextLabel

@OptIn(ExperimentalComposeUiApi::class)
@Composable
internal fun InputComponent(
style: InputComponentViewStyle,
state: InputComponentState,
autofillType: AutofillType? = null,
onFocusChanged: ((Boolean) -> Unit)? = null,
onValueChange: (String) -> Unit
) = with(state) {
Expand All @@ -39,7 +43,7 @@ internal fun InputComponent(
}
}
// Input field
InputField(style.inputFieldStyle, inputFieldState, onFocusChanged, onValueChange)
InputField(style.inputFieldStyle, inputFieldState, autofillType, onFocusChanged, onValueChange)
// Error message
if (errorState.isVisible.value) TextLabel(style.errorMessageStyle, errorState)
}
Expand Down
Original file line number Diff line number Diff line change
@@ -1,9 +1,13 @@
package com.checkout.frames.component.billingaddressfields

import androidx.compose.runtime.Composable
import androidx.compose.ui.ExperimentalComposeUiApi
import androidx.compose.ui.autofill.AutofillType
import com.checkout.frames.component.base.InputComponent
import com.checkout.frames.screen.billingaddress.billingaddressdetails.models.BillingFormFields
import com.checkout.frames.style.view.BillingAddressInputComponentViewStyle

@OptIn(ExperimentalComposeUiApi::class)
@Composable
internal fun BillingAddressDynamicInputComponent(
position: Int,
Expand All @@ -12,9 +16,28 @@ internal fun BillingAddressDynamicInputComponent(
onFocusChanged: (Int, Boolean) -> Unit,
onValueChange: (Int, String) -> Unit
) {
val autofillType = when(inputComponentViewStyle.addressFieldName) {
BillingFormFields.AddressLineOne.name -> {
AutofillType.AddressStreet
}
BillingFormFields.AddressLineTwo.name -> {
AutofillType.AddressAuxiliaryDetails
}
BillingFormFields.City.name -> {
AutofillType.AddressLocality
}
BillingFormFields.State.name -> {
AutofillType.AddressRegion
}
BillingFormFields.PostCode.name -> {
AutofillType.PostalCode
}
else -> null
}
InputComponent(
style = inputComponentViewStyle.inputComponentViewStyle,
state = inputComponentState.inputComponentState,
autofillType = autofillType,
onFocusChanged = { onFocusChanged(position, it) },
onValueChange = { onValueChange(position, it) }
)
Expand Down
Original file line number Diff line number Diff line change
@@ -1,11 +1,14 @@
package com.checkout.frames.component.cardnumber

import androidx.compose.runtime.Composable
import androidx.compose.ui.ExperimentalComposeUiApi
import androidx.compose.ui.autofill.AutofillType
import androidx.lifecycle.viewmodel.compose.viewModel
import com.checkout.frames.component.base.InputComponent
import com.checkout.frames.di.base.Injector
import com.checkout.frames.style.component.CardNumberComponentStyle

@OptIn(ExperimentalComposeUiApi::class)
@Composable
internal fun CardNumberComponent(
style: CardNumberComponentStyle,
Expand All @@ -18,6 +21,7 @@ internal fun CardNumberComponent(
InputComponent(
style = viewModel.componentStyle,
state = viewModel.componentState.inputState,
autofillType = AutofillType.CreditCardNumber,
onFocusChanged = viewModel::onFocusChanged,
onValueChange = viewModel::onCardNumberChange
)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -4,11 +4,13 @@ import androidx.compose.foundation.clickable
import androidx.compose.foundation.interaction.MutableInteractionSource
import androidx.compose.runtime.Composable
import androidx.compose.runtime.remember
import androidx.compose.ui.ExperimentalComposeUiApi
import androidx.lifecycle.viewmodel.compose.viewModel
import com.checkout.frames.component.base.InputComponent
import com.checkout.frames.di.base.Injector
import com.checkout.frames.style.component.CountryComponentStyle

@OptIn(ExperimentalComposeUiApi::class)
@Composable
internal fun CountryComponent(
style: CountryComponentStyle,
Expand Down
Original file line number Diff line number Diff line change
@@ -1,11 +1,14 @@
package com.checkout.frames.component.cvv

import androidx.compose.runtime.Composable
import androidx.compose.ui.ExperimentalComposeUiApi
import androidx.compose.ui.autofill.AutofillType
import androidx.lifecycle.viewmodel.compose.viewModel
import com.checkout.frames.component.base.InputComponent
import com.checkout.frames.di.base.Injector
import com.checkout.frames.style.component.CvvComponentStyle

@OptIn(ExperimentalComposeUiApi::class)
@Composable
internal fun CvvComponent(
style: CvvComponentStyle,
Expand All @@ -20,6 +23,7 @@ internal fun CvvComponent(
InputComponent(
style = viewModel.componentStyle,
state = viewModel.componentState.inputState,
autofillType = AutofillType.CreditCardSecurityCode,
onFocusChanged = viewModel::onFocusChanged,
onValueChange = viewModel::onCvvChange
)
Expand Down
Original file line number Diff line number Diff line change
@@ -1,11 +1,14 @@
package com.checkout.frames.component.expirydate

import androidx.compose.runtime.Composable
import androidx.compose.ui.ExperimentalComposeUiApi
import androidx.compose.ui.autofill.AutofillType
import androidx.lifecycle.viewmodel.compose.viewModel
import com.checkout.frames.component.base.InputComponent
import com.checkout.frames.di.base.Injector
import com.checkout.frames.style.component.ExpiryDateComponentStyle

@OptIn(ExperimentalComposeUiApi::class)
@Composable
internal fun ExpiryDateComponent(
style: ExpiryDateComponentStyle,
Expand All @@ -18,6 +21,7 @@ internal fun ExpiryDateComponent(
InputComponent(
style = viewModel.componentStyle,
state = viewModel.componentState.inputState,
autofillType = AutofillType.CreditCardExpirationDate,
onFocusChanged = viewModel::onFocusChanged,
onValueChange = viewModel::onExpiryDateInputChange
)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -63,6 +63,7 @@ internal fun CountryPickerScreen(
}
}

@OptIn(ExperimentalComposeUiApi::class)
@Composable
private fun HeaderComponent(
screenTitleStyle: TextLabelViewStyle,
Expand All @@ -74,7 +75,7 @@ private fun HeaderComponent(
) {
Column {
TextLabel(style = screenTitleStyle, state = screenTitleState)
InputField(searchFieldStyle, searchFieldState, onSearchFocusChange, onSearchValueChange)
InputField(searchFieldStyle, searchFieldState, null, onSearchFocusChange, onSearchValueChange)
}
}

Expand Down
68 changes: 58 additions & 10 deletions frames/src/main/java/com/checkout/frames/view/InputField.kt
Original file line number Diff line number Diff line change
Expand Up @@ -4,9 +4,9 @@ import android.annotation.SuppressLint
import androidx.compose.foundation.background
import androidx.compose.foundation.interaction.MutableInteractionSource
import androidx.compose.foundation.layout.defaultMinSize
import androidx.compose.foundation.layout.padding
import androidx.compose.foundation.layout.fillMaxWidth
import androidx.compose.foundation.layout.height
import androidx.compose.foundation.layout.padding
import androidx.compose.foundation.shape.CircleShape
import androidx.compose.foundation.shape.CutCornerShape
import androidx.compose.foundation.shape.RoundedCornerShape
Expand All @@ -15,25 +15,32 @@ import androidx.compose.foundation.text.selection.LocalTextSelectionColors
import androidx.compose.foundation.text.selection.TextSelectionColors
import androidx.compose.material3.ExperimentalMaterial3Api
import androidx.compose.material3.LocalTextStyle
import androidx.compose.material3.TextFieldDefaults
import androidx.compose.material3.TextFieldColors
import androidx.compose.material3.Surface
import androidx.compose.material3.MaterialTheme
import androidx.compose.material3.Surface
import androidx.compose.material3.Text
import androidx.compose.material3.TextFieldColors
import androidx.compose.material3.TextFieldDefaults
import androidx.compose.material3.TextFieldDefaults.indicatorLine
import androidx.compose.material3.TextFieldDefaults.outlinedTextFieldColors
import androidx.compose.material3.TextFieldDefaults.textFieldColors
import androidx.compose.runtime.Composable
import androidx.compose.runtime.CompositionLocalProvider
import androidx.compose.runtime.remember
import androidx.compose.runtime.mutableStateOf
import androidx.compose.runtime.remember
import androidx.compose.ui.ExperimentalComposeUiApi
import androidx.compose.ui.Modifier
import androidx.compose.ui.autofill.AutofillNode
import androidx.compose.ui.autofill.AutofillType
import androidx.compose.ui.focus.onFocusChanged
import androidx.compose.ui.graphics.takeOrElse
import androidx.compose.ui.graphics.SolidColor
import androidx.compose.ui.graphics.Color
import androidx.compose.ui.graphics.Shape
import androidx.compose.ui.graphics.RectangleShape
import androidx.compose.ui.graphics.Shape
import androidx.compose.ui.graphics.SolidColor
import androidx.compose.ui.graphics.takeOrElse
import androidx.compose.ui.layout.boundsInWindow
import androidx.compose.ui.layout.onGloballyPositioned
import androidx.compose.ui.platform.LocalAutofill
import androidx.compose.ui.platform.LocalAutofillTree
import androidx.compose.ui.platform.LocalLayoutDirection
import androidx.compose.ui.text.TextStyle
import androidx.compose.ui.text.input.VisualTransformation
Expand All @@ -44,15 +51,16 @@ import androidx.compose.ui.unit.Dp
import androidx.compose.ui.unit.LayoutDirection
import androidx.compose.ui.unit.dp
import com.checkout.frames.model.InputFieldColors
import com.checkout.frames.utils.constants.BorderConstants
import com.checkout.frames.style.view.InputFieldViewStyle
import com.checkout.frames.utils.constants.BorderConstants
import com.checkout.frames.utils.extensions.clearFocusOnKeyboardDismiss

@Composable
@OptIn(ExperimentalMaterial3Api::class)
@OptIn(ExperimentalMaterial3Api::class, ExperimentalComposeUiApi::class)
internal fun InputField(
style: InputFieldViewStyle,
state: InputFieldState,
autofillType: AutofillType? = null,
onFocusChanged: ((Boolean) -> Unit)? = null,
onValueChange: (String) -> Unit
) = with(style) {
Expand All @@ -63,6 +71,26 @@ internal fun InputField(
val textColor = textStyle.color.takeOrElse { colors.textColor(enabled).value }
val mergedTextStyle = textStyle.merge(TextStyle(color = textColor))
val textSelectionColors = provideTextSelectionColors(style.colors, colors.cursorColor(isError = false).value)

var autofillNode: AutofillNode? = null
if (autofillType != null) {
autofillNode = AutofillNode(
autofillTypes = listOf(autofillType),
onFill = {
if (autofillType == AutofillType.CreditCardExpirationDate) {
val date = it.replace("/", "")
state.text.value = date
onValueChange(state.text.value)
return@AutofillNode
}
state.text.value = it
onValueChange(state.text.value)
}
)
LocalAutofillTree.current += autofillNode
}
val autofill = LocalAutofill.current

var modifier = modifier
.clearFocusOnKeyboardDismiss()
.background(colors.containerColor(enabled).value, containerShape)
Expand All @@ -71,6 +99,21 @@ internal fun InputField(
minWidth = TextFieldDefaults.MinWidth,
minHeight = TextFieldDefaults.MinHeight
)
.onGloballyPositioned {
autofillNode?.boundingBox = it.boundsInWindow()
}
.onFocusChanged { focusState ->
if (autofillNode != null) {
autofill?.run {
if (focusState.isFocused) {
requestAutofillForNode(autofillNode)
} else {
cancelAutofillForNode(autofillNode)
}
}
}
}


if (borderShape == null) modifier = modifier.indicatorLine(
enabled,
Expand Down Expand Up @@ -241,6 +284,7 @@ private fun ((String) -> Unit).withMaxLength(maxLength: Int?): (String) -> Unit

/* ------------------------------ Preview ------------------------------ */

@OptIn(ExperimentalComposeUiApi::class)
@SuppressLint("UnrememberedMutableState")
@Preview(showBackground = true, name = "Default InputField")
@Composable
Expand All @@ -266,6 +310,7 @@ private fun InputFieldPreview() {
}
}

@OptIn(ExperimentalComposeUiApi::class)
@SuppressLint("UnrememberedMutableState")
@Preview(showBackground = true, name = "Round InputField")
@Composable
Expand Down Expand Up @@ -296,6 +341,7 @@ private fun RoundInputFieldPreview() {
}
}

@OptIn(ExperimentalComposeUiApi::class)
@SuppressLint("UnrememberedMutableState")
@Preview(showBackground = true, name = "Rectangle OutlineInputField")
@Composable
Expand All @@ -321,6 +367,7 @@ private fun CustomOutlineInputFieldPreview() {
}
}

@OptIn(ExperimentalComposeUiApi::class)
@SuppressLint("UnrememberedMutableState")
@Preview(showBackground = true, name = "Cut corner OutlineInputField")
@Composable
Expand Down Expand Up @@ -348,6 +395,7 @@ private fun CutCornerOutlineInputFieldPreview() {
}
}

@OptIn(ExperimentalComposeUiApi::class)
@SuppressLint("UnrememberedMutableState")
@Preview(showBackground = true, name = "Circle OutlineInputField1")
@Composable
Expand Down