Skip to content

Commit 951c980

Browse files
committed
feat(phone): add IME autofill hints for phone number and OTP fields
Add contentType parameter to TextInput and set PhoneNumber on PhoneInputField and SmsOtpCode on OtpInputField so the keyboard can suggest the user phone number and auto-read SMS OTP codes. Signed-off-by: Brandon McAnsh <git@bmcreations.dev>
1 parent 048f0e6 commit 951c980

4 files changed

Lines changed: 35 additions & 1 deletion

File tree

apps/flipcash/features/contact-verification/src/main/kotlin/com/flipcash/app/contact/verification/internal/phone/PhoneVerificationViewModel.kt

Lines changed: 16 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -109,8 +109,23 @@ internal class PhoneVerificationViewModel @Inject constructor(
109109
.flatMapLatest { ts -> snapshotFlow { ts.text } }
110110
.distinctUntilChanged()
111111
.onEach { enteredNumber ->
112+
val raw = enteredNumber.toString()
113+
114+
// Handle autofilled international numbers (e.g. "+15551234567")
115+
if (raw.contains("+")) {
116+
val result = phoneUtils.parseInternationalNumber(raw)
117+
if (result != null) {
118+
val (locale, nationalNumber) = result
119+
dispatchEvent(Event.OnCountrySelected(locale))
120+
stateFlow.value.numberTextFieldState.edit {
121+
replace(0, length, nationalNumber)
122+
}
123+
return@onEach
124+
}
125+
}
126+
112127
val countryCode = stateFlow.value.selectedLocale.phoneCode.toString()
113-
val phoneInputFiltered = enteredNumber.toString().replace("+$countryCode", "")
128+
val phoneInputFiltered = raw.replace("+$countryCode", "")
114129
val phoneNumber = "+$countryCode$phoneInputFiltered"
115130
val formattedNumber = phoneUtils.formatNumber(
116131
number = phoneNumber,

apps/flipcash/shared/phone/src/main/kotlin/com/flipcash/app/phone/components/OtpInputField.kt

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -31,6 +31,7 @@ import androidx.compose.ui.graphics.Color
3131
import androidx.compose.ui.text.input.KeyboardType
3232
import androidx.compose.ui.tooling.preview.Preview
3333
import com.flipcash.app.theme.FlipcashPreview
34+
import androidx.compose.ui.autofill.ContentType
3435
import com.getcode.theme.CodeTheme
3536
import com.getcode.theme.WindowSizeClass
3637
import com.getcode.ui.components.TextInput
@@ -73,6 +74,7 @@ fun OtpInputField(
7374
}
7475
},
7576
state = state,
77+
contentType = ContentType.SmsOtpCode,
7678
keyboardOptions = KeyboardOptions.Default.copy(keyboardType = KeyboardType.Number),
7779
)
7880

apps/flipcash/shared/phone/src/main/kotlin/com/flipcash/app/phone/components/PhoneInputField.kt

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -41,6 +41,7 @@ import com.flipcash.shared.phone.R
4141
import com.getcode.opencode.compose.ExchangeStub
4242
import com.getcode.opencode.compose.LocalExchange
4343
import com.getcode.theme.CodeTheme
44+
import androidx.compose.ui.autofill.ContentType
4445
import com.getcode.ui.components.TextInput
4546
import com.getcode.ui.components.VerticalDivider
4647
import com.getcode.ui.core.rememberAnimationScale
@@ -78,6 +79,7 @@ fun PhoneInputField(
7879
onKeyboardAction = { onSubmit() },
7980
maxLines = 1,
8081
placeholder = placeholder,
82+
contentType = ContentType.PhoneNumber + ContentType.PhoneNumberDevice,
8183
outputTransformation = outputTransformation,
8284
leadingIcon = {
8385
Row {

ui/components/src/main/kotlin/com/getcode/ui/components/TextInput.kt

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -42,6 +42,9 @@ import androidx.compose.ui.text.TextStyle
4242
import androidx.compose.ui.tooling.preview.Preview
4343
import androidx.compose.ui.tooling.preview.datasource.LoremIpsum
4444
import androidx.compose.ui.unit.Dp
45+
import androidx.compose.ui.autofill.ContentType
46+
import androidx.compose.ui.semantics.contentType
47+
import androidx.compose.ui.semantics.semantics
4548
import androidx.compose.ui.unit.dp
4649
import com.getcode.theme.CodeTheme
4750
import com.getcode.theme.DesignSystem
@@ -80,6 +83,7 @@ fun TextInput(
8083
constraintMode: ConstraintMode = ConstraintMode.Free,
8184
leadingIcon: (@Composable () -> Unit)? = null,
8285
trailingIcon: (@Composable () -> Unit)? = null,
86+
contentType: ContentType? = null,
8387
scrollState: ScrollState = rememberScrollState(),
8488
) {
8589
val backgroundColor by colors.backgroundColor(enabled = enabled)
@@ -93,9 +97,20 @@ fun TextInput(
9397

9498
var textSize by remember(style.fontSize) { mutableStateOf(style.fontSize) }
9599

100+
// Capture outside semantics lambda to avoid shadowing by
101+
// SemanticsPropertyReceiver.contentType extension property.
102+
val autofillContentType = contentType
103+
96104
Box(modifier = modifier) {
97105
BasicTextField(
98106
modifier = Modifier
107+
.then(
108+
if (autofillContentType != null) {
109+
Modifier.semantics { this.contentType = autofillContentType }
110+
} else {
111+
Modifier
112+
}
113+
)
99114
.background(backgroundColor, shape)
100115
.defaultMinSize(minHeight = minHeight)
101116
.constrain(

0 commit comments

Comments
 (0)