@@ -34,6 +34,7 @@ import androidx.compose.runtime.derivedStateOf
3434import androidx.compose.runtime.getValue
3535import androidx.compose.runtime.mutableStateOf
3636import androidx.compose.runtime.remember
37+ import androidx.compose.runtime.rememberCoroutineScope
3738import androidx.compose.runtime.setValue
3839import androidx.compose.ui.Modifier
3940import androidx.compose.ui.focus.FocusDirection
@@ -46,17 +47,17 @@ import androidx.compose.ui.res.stringResource
4647import androidx.compose.ui.semantics.error
4748import androidx.compose.ui.semantics.semantics
4849import androidx.compose.ui.text.TextRange
49- import androidx.compose.ui.text.buildAnnotatedString
5050import androidx.compose.ui.text.input.ImeAction
5151import androidx.compose.ui.text.input.KeyboardType
52- import androidx.compose.ui.text.input.OffsetMapping
5352import androidx.compose.ui.text.input.TextFieldValue
54- import androidx.compose.ui.text.input.TransformedText
55- import androidx.compose.ui.text.input.VisualTransformation
5653import com.google.android.fhir.datacapture.R
5754import com.google.android.fhir.datacapture.extensions.format
5855import com.google.android.fhir.datacapture.extensions.toLocalDate
5956import java.time.LocalDate
57+ import kotlinx.coroutines.Dispatchers
58+ import kotlinx.coroutines.Job
59+ import kotlinx.coroutines.delay
60+ import kotlinx.coroutines.launch
6061
6162@OptIn(ExperimentalMaterial3Api ::class )
6263@Composable
@@ -76,43 +77,73 @@ internal fun DatePickerItem(
7677 val focusManager = LocalFocusManager .current
7778 val keyboardController = LocalSoftwareKeyboardController .current
7879 var dateInputState by remember(dateInput) { mutableStateOf(dateInput) }
80+ var deletionDetected by remember(dateInput) { mutableStateOf(false ) }
81+ var typingJob by remember { mutableStateOf<Job ?>(null ) }
82+ val coroutineScope = rememberCoroutineScope { Dispatchers .Main }
7983 val dateInputDisplay by
8084 remember(dateInputState) {
8185 derivedStateOf {
86+ val text =
87+ if (! dateInputFormat.delimiterExistsInPattern || deletionDetected) {
88+ dateInputState.display
89+ } else {
90+ buildString {
91+ append(dateInputState.display)
92+ if (
93+ this .length > dateInputFormat.delimiterFirstIndex &&
94+ get(dateInputFormat.delimiterFirstIndex) != dateInputFormat.delimiter
95+ ) {
96+ insert(dateInputFormat.delimiterFirstIndex, dateInputFormat.delimiter)
97+ }
98+ if (
99+ this .length > dateInputFormat.delimiterLastIndex &&
100+ dateInputFormat.delimiterLastIndex > dateInputFormat.delimiterFirstIndex &&
101+ get(dateInputFormat.delimiterLastIndex) != dateInputFormat.delimiter
102+ ) {
103+ insert(dateInputFormat.delimiterLastIndex, dateInputFormat.delimiter)
104+ }
105+ }
106+ }
82107 TextFieldValue (
83- text = dateInputState.display ,
108+ text = text ,
84109 selection = TextRange (dateInputFormat.patternWithDelimiters.length),
85110 )
86111 }
87112 }
88113
89114 var showDatePickerModal by remember { mutableStateOf(false ) }
90115
91- LaunchedEffect (dateInputState) {
92- if (dateInputState != dateInput) {
93- onDateInputEntry(dateInputState)
94- }
95- }
96-
97116 OutlinedTextField (
98117 value = dateInputDisplay,
99118 onValueChange = {
100119 val text = it.text
120+ deletionDetected = text.length < dateInputState.display.length
101121 if (
102- text.length <= dateInputFormat.patternWithoutDelimiters .length &&
103- text.all { char -> char.isDigit() }
122+ text.length <= dateInputFormat.patternWithDelimiters .length &&
123+ text.all { char -> char.isDigit() || char == dateInputFormat.delimiter }
104124 ) {
105125 val trimmedText = text.trim()
106126 val localDate =
107127 if (
108128 trimmedText.isNotBlank() &&
109- trimmedText.length == dateInputFormat.patternWithoutDelimiters .length
129+ trimmedText.length == dateInputFormat.patternWithDelimiters .length
110130 ) {
111- parseStringToLocalDate(trimmedText, dateInputFormat.patternWithoutDelimiters )
131+ parseStringToLocalDate(trimmedText, dateInputFormat.patternWithDelimiters )
112132 } else {
113133 null
114134 }
115- dateInputState = DateInput (text, localDate)
135+ val newDateInput = DateInput (text, localDate)
136+ dateInputState = newDateInput
137+
138+ typingJob?.cancel() // Cancel previous debounce
139+ typingJob =
140+ coroutineScope.launch {
141+ delay(HANDLE_INPUT_DEBOUNCE_TIME ) // Debounce delay (e.g., 500ms)
142+ if (newDateInput != dateInput) {
143+ onDateInputEntry(newDateInput)
144+ }
145+ // Perform actions after user stops typing
146+ }
116147 }
117148 },
118149 singleLine = true ,
@@ -147,25 +178,6 @@ internal fun DatePickerItem(
147178 KeyboardActions (
148179 onNext = { focusManager.moveFocus(FocusDirection .Down ) },
149180 ),
150- visualTransformation =
151- if (! dateInputFormat.delimiterExistsInPattern) {
152- VisualTransformation .None
153- } else {
154- VisualTransformation { originalText ->
155- val text = buildAnnotatedString {
156- originalText.forEachIndexed { index, ch ->
157- append(ch)
158- if (
159- index + 1 == dateInputFormat.delimiterFirstIndex ||
160- index + 2 == dateInputFormat.delimiterLastIndex
161- ) {
162- append(dateInputFormat.delimiter)
163- }
164- }
165- }
166- TransformedText (text, dateInputFormat.offsetMapping)
167- }
168- },
169181 )
170182
171183 if (selectableDates != null && showDatePickerModal) {
@@ -176,7 +188,7 @@ internal fun DatePickerItem(
176188 dateMillis?.toLocalDate()?.let {
177189 dateInputState =
178190 DateInput (
179- display = it.format(dateInputFormat.patternWithoutDelimiters ),
191+ display = it.format(dateInputFormat.patternWithDelimiters ),
180192 value = it,
181193 )
182194 }
@@ -236,29 +248,6 @@ data class DateInputFormat(val patternWithDelimiters: String, val delimiter: Cha
236248 val delimiterFirstIndex: Int = patternWithDelimiters.indexOf(delimiter)
237249 val delimiterLastIndex: Int = patternWithDelimiters.lastIndexOf(delimiter)
238250 val delimiterExistsInPattern = delimiterFirstIndex != - 1 && delimiterLastIndex != - 1
239-
240- val offsetMapping =
241- object : OffsetMapping {
242- override fun originalToTransformed (offset : Int ): Int {
243- return when {
244- delimiterExistsInPattern &&
245- offset >= delimiterLastIndex &&
246- delimiterLastIndex > delimiterFirstIndex -> offset + 2
247- delimiterExistsInPattern && offset >= delimiterFirstIndex -> offset + 1
248- else -> offset
249- }
250- }
251-
252- override fun transformedToOriginal (offset : Int ): Int {
253- return when {
254- delimiterExistsInPattern &&
255- offset >= delimiterLastIndex &&
256- offset > delimiterFirstIndex -> offset - 2
257- delimiterExistsInPattern && offset >= delimiterFirstIndex -> offset - 1
258- else -> offset
259- }
260- }
261- }
262251}
263252
264253const val DATE_TEXT_INPUT_FIELD = " date_picker_text_field"
0 commit comments