blob: b0ac2496c863ca138a3c79257ad7153fd4ad2ed6 [file] [log] [blame]
/*
* Copyright 2023 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
@file:OptIn(ExperimentalFoundationApi::class)
package androidx.compose.foundation.text2.input
import androidx.compose.foundation.ExperimentalFoundationApi
import androidx.compose.foundation.text.KeyboardOptions
import androidx.compose.runtime.Stable
/**
* A function that is ran after every change made to a [TextFieldState] by user input and can change
* or reject that input.
*
* Filters are ran after hardware and software keyboard events, when text is pasted or dropped into
* the field, or when an accessibility service changes the text.
*
* To chain filters together, call [then].
*
* Prebuilt filters are provided for common filter operations. See:
* - `TextEditFilter`.[maxLengthInChars]`()`
* - `TextEditFilter`.[maxLengthInCodepoints]`()`
*
* @sample androidx.compose.foundation.samples.BasicTextField2CustomFilterSample
*/
@ExperimentalFoundationApi
@Stable
fun interface TextEditFilter {
/**
* Optional [KeyboardOptions] that will be used as the default keyboard options for configuring
* the IME. The options passed directly to the text field composable will always override this.
*/
val keyboardOptions: KeyboardOptions? get() = null
/**
* The filter operation. For more information see the documentation on [TextEditFilter].
*
* To reject all changes in [newState], call
* `newState.`[revertAllChanges][MutableTextFieldValueWithSelection.revertAllChanges].
*
* @param originalValue The value of the field before the change was performed.
* @param valueWithChanges The value of the field after the change. This value can be changed
* in-place to alter or reject the changes or set the selection.
*/
fun filter(originalValue: TextFieldCharSequence, valueWithChanges: TextFieldBufferWithSelection)
companion object
}
/**
* Creates a filter chain that will run [next] after this. Filters are applied in order – [next]
* will see any the changes made by this filter.
*
* @sample androidx.compose.foundation.samples.BasicTextField2FilterChainingSample
*
* @param next The [TextEditFilter] that will be ran after this one.
* @param keyboardOptions The [KeyboardOptions] options to use for the chained filter. If not
* specified, the chained filter will not specify any [KeyboardOptions], even if one or both of
* this or [next] specified some.
*/
@ExperimentalFoundationApi
@Stable
fun TextEditFilter.then(
next: TextEditFilter,
keyboardOptions: KeyboardOptions? = null
): TextEditFilter = FilterChain(this, next, keyboardOptions)
private class FilterChain(
private val first: TextEditFilter,
private val second: TextEditFilter,
override val keyboardOptions: KeyboardOptions?
) : TextEditFilter {
override fun filter(
originalValue: TextFieldCharSequence,
valueWithChanges: TextFieldBufferWithSelection
) {
first.filter(originalValue, valueWithChanges)
second.filter(originalValue, valueWithChanges)
}
override fun toString(): String = "$first.then($second)"
override fun equals(other: Any?): Boolean {
if (this === other) return true
if (javaClass != other?.javaClass) return false
other as FilterChain
if (first != other.first) return false
if (second != other.second) return false
if (keyboardOptions != other.keyboardOptions) return false
return true
}
override fun hashCode(): Int {
var result = first.hashCode()
result = 31 * result + second.hashCode()
result = 32 * result + keyboardOptions.hashCode()
return result
}
}