1 /* 2 * Copyright 2020 The Android Open Source Project 3 * 4 * Licensed under the Apache License, Version 2.0 (the "License"); 5 * you may not use this file except in compliance with the License. 6 * You may obtain a copy of the License at 7 * 8 * http://www.apache.org/licenses/LICENSE-2.0 9 * 10 * Unless required by applicable law or agreed to in writing, software 11 * distributed under the License is distributed on an "AS IS" BASIS, 12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 * See the License for the specific language governing permissions and 14 * limitations under the License. 15 */ 16 17 package androidx.compose.foundation.text 18 19 import androidx.compose.runtime.Immutable 20 import androidx.compose.runtime.Stable 21 import androidx.compose.ui.text.input.ImeAction 22 import androidx.compose.ui.text.input.ImeOptions 23 import androidx.compose.ui.text.input.KeyboardCapitalization 24 import androidx.compose.ui.text.input.KeyboardType 25 import androidx.compose.ui.text.input.PlatformImeOptions 26 import androidx.compose.ui.text.intl.LocaleList 27 28 /** 29 * The keyboard configuration options for TextFields. It is not guaranteed if software keyboard will 30 * comply with the options provided here. 31 * 32 * @param capitalization informs the keyboard whether to automatically capitalize characters, words 33 * or sentences. Only applicable to only text based [KeyboardType]s such as [KeyboardType.Text], 34 * [KeyboardType.Ascii]. It will not be applied to [KeyboardType]s such as [KeyboardType.Number]. 35 * @param autoCorrectEnabled informs the keyboard whether to enable auto correct. Only applicable to 36 * text based [KeyboardType]s such as [KeyboardType.Email], [KeyboardType.Uri]. It will not be 37 * applied to [KeyboardType]s such as [KeyboardType.Number]. Most of keyboard implementations 38 * ignore this value for [KeyboardType]s such as [KeyboardType.Text]. A null value (the default 39 * parameter value) means autocorrect will be enabled. 40 * @param keyboardType The keyboard type to be used in this text field. Note that this input type is 41 * honored by keyboard and shows corresponding keyboard but this is not guaranteed. For example, 42 * some keyboards may send non-ASCII character even if you set [KeyboardType.Ascii]. 43 * @param imeAction The IME action. This IME action is honored by keyboard and may show specific 44 * icons on the keyboard. For example, search icon may be shown if [ImeAction.Search] is 45 * specified. When [ImeOptions.singleLine] is false, the keyboard might show return key rather 46 * than the action requested here. 47 * @param platformImeOptions defines the platform specific IME options. 48 * @param showKeyboardOnFocus when true, software keyboard will show on focus gain. When false, the 49 * user must interact (e.g. tap) before the keyboard is shown. A null value (the default parameter 50 * value) means the keyboard will be shown on focus. 51 * @param hintLocales List of the languages that the user is supposed to switch to no matter what 52 * input method subtype is currently used. This special "hint" can be used mainly for, but not 53 * limited to, multilingual users who want IMEs to switch language based on editor's context. Pass 54 * null to express the intention that a specific hint should not be set. 55 */ 56 @Immutable 57 class KeyboardOptions( 58 val capitalization: KeyboardCapitalization = KeyboardCapitalization.Unspecified, 59 @Suppress("AutoBoxing") @get:Suppress("AutoBoxing") val autoCorrectEnabled: Boolean? = null, 60 val keyboardType: KeyboardType = KeyboardType.Unspecified, 61 val imeAction: ImeAction = ImeAction.Unspecified, 62 val platformImeOptions: PlatformImeOptions? = null, 63 @Suppress("AutoBoxing") @get:Suppress("AutoBoxing") val showKeyboardOnFocus: Boolean? = null, 64 @get:Suppress("NullableCollection") val hintLocales: LocaleList? = null, 65 ) { 66 67 companion object { 68 /** Default [KeyboardOptions]. Please see parameter descriptions for default values. */ 69 @Stable val Default = KeyboardOptions() 70 71 /** Default [KeyboardOptions] for [BasicSecureTextField]. */ 72 @Stable 73 internal val SecureTextField = 74 KeyboardOptions(autoCorrectEnabled = false, keyboardType = KeyboardType.Password) 75 } 76 77 @Deprecated( 78 "Please use the new constructor that takes optional autoCorrectEnabled parameter.", 79 level = DeprecationLevel.WARNING, 80 replaceWith = 81 ReplaceWith( 82 "KeyboardOptions(" + 83 "capitalization = capitalization, " + 84 "autoCorrectEnabled = autoCorrect, " + 85 "keyboardType = keyboardType, " + 86 "imeAction = imeAction," + 87 "platformImeOptions = platformImeOptions, " + 88 "showKeyboardOnFocus = showKeyboardOnFocus," + 89 "hintLocales = hintLocales" + 90 ")" 91 ) 92 ) 93 constructor( 94 capitalization: KeyboardCapitalization = KeyboardCapitalization.Unspecified, 95 autoCorrect: Boolean, 96 keyboardType: KeyboardType = KeyboardType.Unspecified, 97 imeAction: ImeAction = ImeAction.Unspecified, 98 platformImeOptions: PlatformImeOptions? = null, 99 @Suppress("AutoBoxing") showKeyboardOnFocus: Boolean? = null, 100 @Suppress("NullableCollection") hintLocales: LocaleList? = null, 101 ) : this( 102 capitalization = capitalization, 103 autoCorrectEnabled = autoCorrect, 104 keyboardType = keyboardType, 105 imeAction = imeAction, 106 platformImeOptions = platformImeOptions, 107 showKeyboardOnFocus = showKeyboardOnFocus, 108 hintLocales = hintLocales, 109 ) 110 111 @Deprecated( 112 "Please use the new constructor that takes optional platformImeOptions parameter.", 113 level = DeprecationLevel.HIDDEN 114 ) 115 constructor( 116 capitalization: KeyboardCapitalization = KeyboardCapitalization.Unspecified, 117 autoCorrect: Boolean = Default.autoCorrectOrDefault, 118 keyboardType: KeyboardType = KeyboardType.Unspecified, 119 imeAction: ImeAction = ImeAction.Default 120 ) : this( 121 capitalization = capitalization, 122 autoCorrectEnabled = autoCorrect, 123 keyboardType = keyboardType, 124 imeAction = imeAction, 125 platformImeOptions = null 126 ) 127 128 @Deprecated("Maintained for binary compat", level = DeprecationLevel.HIDDEN) 129 constructor( 130 capitalization: KeyboardCapitalization = KeyboardCapitalization.None, 131 autoCorrect: Boolean = Default.autoCorrectOrDefault, 132 keyboardType: KeyboardType = KeyboardType.Text, 133 imeAction: ImeAction = ImeAction.Default, 134 platformImeOptions: PlatformImeOptions? = null 135 ) : this( 136 capitalization = capitalization, 137 autoCorrectEnabled = autoCorrect, 138 keyboardType = keyboardType, 139 imeAction = imeAction, 140 platformImeOptions = platformImeOptions, 141 showKeyboardOnFocus = Default.showKeyboardOnFocusOrDefault 142 ) 143 144 @Deprecated("Please use the autoCorrectEnabled property.", level = DeprecationLevel.WARNING) 145 val autoCorrect: Boolean 146 get() = autoCorrectOrDefault 147 148 // Suppress GetterSetterNames because this is how the property was named previously. 149 @Suppress("unused", "GetterSetterNames") 150 @get:Suppress("GetterSetterNames") 151 @Deprecated( 152 "Included for binary compatibility. Use showKeyboardOnFocus.", 153 level = DeprecationLevel.HIDDEN 154 ) 155 val shouldShowKeyboardOnFocus: Boolean 156 get() = showKeyboardOnFocus ?: true 157 158 private val autoCorrectOrDefault: Boolean 159 get() = autoCorrectEnabled ?: true 160 161 private val capitalizationOrDefault: KeyboardCapitalization 162 get() = <lambda>null163 capitalization.takeUnless { it == KeyboardCapitalization.Unspecified } 164 ?: KeyboardCapitalization.None 165 166 private val keyboardTypeOrDefault: KeyboardType <lambda>null167 get() = keyboardType.takeUnless { it == KeyboardType.Unspecified } ?: KeyboardType.Text 168 169 internal val imeActionOrDefault: ImeAction <lambda>null170 get() = imeAction.takeUnless { it == ImeAction.Unspecified } ?: ImeAction.Default 171 172 internal val showKeyboardOnFocusOrDefault: Boolean 173 get() = showKeyboardOnFocus ?: true 174 175 private val hintLocalesOrDefault: LocaleList 176 get() = hintLocales ?: LocaleList.Empty 177 178 private val isCompletelyUnspecified: Boolean 179 get() = 180 capitalization == KeyboardCapitalization.Unspecified && 181 autoCorrectEnabled == null && 182 keyboardType == KeyboardType.Unspecified && 183 imeAction == ImeAction.Unspecified && 184 platformImeOptions == null && 185 showKeyboardOnFocus == null && 186 hintLocales == null 187 188 /** 189 * Returns a new [ImeOptions] with the values that are in this [KeyboardOptions] and provided 190 * params. 191 * 192 * @param singleLine see [ImeOptions.singleLine] 193 */ toImeOptionsnull194 internal fun toImeOptions(singleLine: Boolean = ImeOptions.Default.singleLine) = 195 ImeOptions( 196 singleLine = singleLine, 197 capitalization = capitalizationOrDefault, 198 autoCorrect = autoCorrectOrDefault, 199 keyboardType = keyboardTypeOrDefault, 200 imeAction = imeActionOrDefault, 201 platformImeOptions = platformImeOptions, 202 hintLocales = hintLocalesOrDefault 203 ) 204 205 /** 206 * Returns a copy of this object with the values passed to this method. 207 * 208 * Note that if an unspecified (null) value is passed explicitly to this method, it will replace 209 * any actually-specified value. This differs from the behavior of [merge], which will never 210 * take an unspecified value over a specified one. 211 */ 212 fun copy( 213 capitalization: KeyboardCapitalization = this.capitalization, 214 @Suppress("AutoBoxing") autoCorrectEnabled: Boolean? = this.autoCorrectEnabled, 215 keyboardType: KeyboardType = this.keyboardType, 216 imeAction: ImeAction = this.imeAction, 217 platformImeOptions: PlatformImeOptions? = this.platformImeOptions, 218 @Suppress("AutoBoxing") showKeyboardOnFocus: Boolean? = null, 219 hintLocales: LocaleList? = null 220 ): KeyboardOptions { 221 return KeyboardOptions( 222 capitalization = capitalization, 223 autoCorrectEnabled = autoCorrectEnabled, 224 keyboardType = keyboardType, 225 imeAction = imeAction, 226 platformImeOptions = platformImeOptions, 227 showKeyboardOnFocus = showKeyboardOnFocus, 228 hintLocales = hintLocales 229 ) 230 } 231 232 @Deprecated( 233 "Please use the copy function that takes an autoCorrectEnabled parameter.", 234 level = DeprecationLevel.HIDDEN, 235 replaceWith = 236 ReplaceWith( 237 "copy(" + 238 "capitalization = capitalization, " + 239 "autoCorrectEnabled = autoCorrect, " + 240 "keyboardType = keyboardType, " + 241 "imeAction = imeAction," + 242 "platformImeOptions = platformImeOptions, " + 243 "showKeyboardOnFocus = showKeyboardOnFocus ?: true," + 244 "hintLocales = hintLocales" + 245 ")" 246 ) 247 ) copynull248 fun copy( 249 capitalization: KeyboardCapitalization = this.capitalization, 250 autoCorrect: Boolean = this.autoCorrectOrDefault, 251 keyboardType: KeyboardType = this.keyboardType, 252 imeAction: ImeAction = this.imeAction, 253 platformImeOptions: PlatformImeOptions? = this.platformImeOptions, 254 @Suppress("AutoBoxing") showKeyboardOnFocus: Boolean? = this.showKeyboardOnFocusOrDefault, 255 hintLocales: LocaleList? = this.hintLocales 256 ): KeyboardOptions { 257 return KeyboardOptions( 258 capitalization = capitalization, 259 autoCorrectEnabled = autoCorrect, 260 keyboardType = keyboardType, 261 imeAction = imeAction, 262 platformImeOptions = platformImeOptions, 263 showKeyboardOnFocus = showKeyboardOnFocus, 264 hintLocales = hintLocales 265 ) 266 } 267 268 @Deprecated("Maintained for binary compatibility", level = DeprecationLevel.HIDDEN) copynull269 fun copy( 270 capitalization: KeyboardCapitalization = this.capitalization, 271 autoCorrect: Boolean = this.autoCorrectOrDefault, 272 keyboardType: KeyboardType = this.keyboardType, 273 imeAction: ImeAction = this.imeAction, 274 platformImeOptions: PlatformImeOptions? = this.platformImeOptions 275 ): KeyboardOptions { 276 return KeyboardOptions( 277 capitalization = capitalization, 278 autoCorrectEnabled = autoCorrect, 279 keyboardType = keyboardType, 280 imeAction = imeAction, 281 platformImeOptions = platformImeOptions, 282 showKeyboardOnFocus = this.showKeyboardOnFocus, 283 hintLocales = this.hintLocales 284 // New properties must be added here even though this is deprecated. The deprecated copy 285 // constructors should still work on instances created with newer library versions. 286 ) 287 } 288 289 @Deprecated( 290 "Please use the new copy function that takes optional platformImeOptions parameter.", 291 level = DeprecationLevel.HIDDEN 292 ) copynull293 fun copy( 294 capitalization: KeyboardCapitalization = this.capitalization, 295 autoCorrect: Boolean = this.autoCorrectOrDefault, 296 keyboardType: KeyboardType = this.keyboardType, 297 imeAction: ImeAction = this.imeAction 298 ): KeyboardOptions { 299 return KeyboardOptions( 300 capitalization = capitalization, 301 autoCorrectEnabled = autoCorrect, 302 keyboardType = keyboardType, 303 imeAction = imeAction, 304 platformImeOptions = this.platformImeOptions, 305 showKeyboardOnFocus = this.showKeyboardOnFocus, 306 hintLocales = this.hintLocales 307 // New properties must be added here even though this is deprecated. The deprecated copy 308 // constructors should still work on instances created with newer library versions. 309 ) 310 } 311 equalsnull312 override fun equals(other: Any?): Boolean { 313 if (this === other) return true 314 if (other !is KeyboardOptions) return false 315 316 if (capitalization != other.capitalization) return false 317 if (autoCorrectEnabled != other.autoCorrectEnabled) return false 318 if (keyboardType != other.keyboardType) return false 319 if (imeAction != other.imeAction) return false 320 if (platformImeOptions != other.platformImeOptions) return false 321 if (showKeyboardOnFocus != other.showKeyboardOnFocus) return false 322 if (hintLocales != other.hintLocales) return false 323 324 return true 325 } 326 hashCodenull327 override fun hashCode(): Int { 328 var result = capitalization.hashCode() 329 result = 31 * result + autoCorrectEnabled.hashCode() 330 result = 31 * result + keyboardType.hashCode() 331 result = 31 * result + imeAction.hashCode() 332 result = 31 * result + platformImeOptions.hashCode() 333 result = 31 * result + showKeyboardOnFocus.hashCode() 334 result = 31 * result + hintLocales.hashCode() 335 return result 336 } 337 toStringnull338 override fun toString(): String { 339 return "KeyboardOptions(" + 340 "capitalization=$capitalization, " + 341 "autoCorrectEnabled=$autoCorrectEnabled, " + 342 "keyboardType=$keyboardType, " + 343 "imeAction=$imeAction, " + 344 "platformImeOptions=$platformImeOptions" + 345 "showKeyboardOnFocus=$showKeyboardOnFocus, " + 346 "hintLocales=$hintLocales" + 347 ")" 348 } 349 350 /** 351 * Returns a new [KeyboardOptions] that is a combination of this options and a given [other] 352 * options. 353 * 354 * [other]s null or `Unspecified` properties are replaced with the non-null properties of this 355 * object. 356 * 357 * If the either this or [other] is null, returns the non-null one. 358 */ 359 // TODO(b/331222000) Rename to be more clear about precedence. mergenull360 fun merge(other: KeyboardOptions?): KeyboardOptions = 361 other?.fillUnspecifiedValuesWith(this) ?: this 362 363 /** 364 * Returns a new [KeyboardOptions] that is a combination of this options and a given [other] 365 * options. 366 * 367 * This object's null or `Unspecified` properties are replaced with the non-null properties of 368 * [other]. This differs from the behavior of [copy], which always takes the passed value over 369 * the current one, even if an unspecified value is passed. 370 * 371 * If the either this or [other] is null, returns the non-null one. 372 */ 373 @Stable 374 internal fun fillUnspecifiedValuesWith(other: KeyboardOptions?): KeyboardOptions { 375 // Don't allocate unless necessary. 376 if (other == null || other.isCompletelyUnspecified || other == this) return this 377 if (this.isCompletelyUnspecified) return other 378 379 return KeyboardOptions( 380 capitalization = 381 this.capitalization.takeUnless { it == KeyboardCapitalization.Unspecified } 382 ?: other.capitalization, 383 autoCorrectEnabled = this.autoCorrectEnabled ?: other.autoCorrectEnabled, 384 keyboardType = 385 this.keyboardType.takeUnless { it == KeyboardType.Unspecified } 386 ?: other.keyboardType, 387 imeAction = 388 this.imeAction.takeUnless { it == ImeAction.Unspecified } ?: other.imeAction, 389 platformImeOptions = this.platformImeOptions ?: other.platformImeOptions, 390 showKeyboardOnFocus = this.showKeyboardOnFocus ?: other.showKeyboardOnFocus, 391 hintLocales = this.hintLocales ?: other.hintLocales 392 ) 393 } 394 } 395