Introduce VisualTransform to the InputField
VisualTransform is a filter for changing visual output of the InputField.
This can be used for password field or credit card number input, etc.
Bug: 137785986
Test: ./gradlew test
Change-Id: Id65d6ec96c3b3a9fd9d9d40240e774dcd698ccf2
diff --git a/.idea/codeStyles/Project.xml b/.idea/codeStyles/Project.xml
index 1ecc48eb..4695ff5 100644
--- a/.idea/codeStyles/Project.xml
+++ b/.idea/codeStyles/Project.xml
@@ -1,5 +1,7 @@
<component name="ProjectCodeStyleConfiguration">
<code_scheme name="Project" version="173">
+ <option name="RIGHT_MARGIN" value="100" />
+ <option name="WRAP_WHEN_TYPING_REACHES_RIGHT_MARGIN" value="true" />
<JavaCodeStyleSettings>
<option name="FIELD_NAME_PREFIX" value="m" />
<option name="STATIC_FIELD_NAME_PREFIX" value="s" />
diff --git a/.idea/codeStyles/codeStyleConfig.xml b/.idea/codeStyles/codeStyleConfig.xml
index 03661d9..79ee123 100644
--- a/.idea/codeStyles/codeStyleConfig.xml
+++ b/.idea/codeStyles/codeStyleConfig.xml
@@ -1,6 +1,5 @@
<component name="ProjectCodeStyleConfiguration">
<state>
<option name="USE_PER_PROJECT_SETTINGS" value="true" />
- <option name="PREFERRED_PROJECT_CODE_STYLE" value="AndroidStyle" />
</state>
</component>
\ No newline at end of file
diff --git a/ui/ui-framework/api/1.0.0-alpha01.txt b/ui/ui-framework/api/1.0.0-alpha01.txt
index cbd3f86..b53ffe0 100644
--- a/ui/ui-framework/api/1.0.0-alpha01.txt
+++ b/ui/ui-framework/api/1.0.0-alpha01.txt
@@ -34,7 +34,7 @@
public final class InputFieldKt {
ctor public InputFieldKt();
- method public static void InputField(androidx.ui.input.EditorState value, androidx.ui.core.EditorStyle editorStyle, androidx.ui.input.KeyboardType keyboardType = KeyboardType.Text, androidx.ui.input.ImeAction imeAction = ImeAction.Unspecified, kotlin.jvm.functions.Function1<? super androidx.ui.input.EditorState,kotlin.Unit> onValueChange = {}, kotlin.jvm.functions.Function1<? super androidx.ui.input.ImeAction,kotlin.Unit> onImeActionPerformed = {});
+ method public static void InputField(androidx.ui.input.EditorState value, androidx.ui.core.EditorStyle editorStyle, androidx.ui.input.KeyboardType keyboardType = KeyboardType.Text, androidx.ui.input.ImeAction imeAction = ImeAction.Unspecified, kotlin.jvm.functions.Function1<? super androidx.ui.input.EditorState,kotlin.Unit> onValueChange = {}, kotlin.jvm.functions.Function1<? super androidx.ui.input.ImeAction,kotlin.Unit> onImeActionPerformed = {}, androidx.ui.core.VisualTransformation? visualTransformation = null);
}
public final class IntrinsicMeasurementsReceiver implements androidx.ui.core.DensityReceiver {
@@ -92,6 +92,11 @@
property public abstract Object? parentData;
}
+ public interface OffsetMap {
+ method public int originalToTransformed(int offset);
+ method public int transformedToOriginal(int offset);
+ }
+
public final class OpacityKt {
ctor public OpacityKt();
method public static void Opacity(@FloatRange(from=0.0, to=1.0) float opacity, kotlin.jvm.functions.Function0<kotlin.Unit> children);
@@ -102,6 +107,13 @@
method public static void ParentData(Object data, kotlin.jvm.functions.Function0<kotlin.Unit> children);
}
+ public final class PasswordVisualTransformation implements androidx.ui.core.VisualTransformation {
+ ctor public PasswordVisualTransformation(char mask);
+ ctor public PasswordVisualTransformation();
+ method public androidx.ui.core.TransformedText filter(androidx.ui.text.AnnotatedString text);
+ method public char getMask();
+ }
+
public abstract class Placeable {
ctor public Placeable();
method public abstract androidx.ui.core.IntPx getHeight();
@@ -174,6 +186,23 @@
method public androidx.ui.core.TextSpanComposition getComposer();
}
+ public final class TransformedText {
+ ctor public TransformedText(androidx.ui.text.AnnotatedString transformedText, androidx.ui.core.OffsetMap offsetMap);
+ method public androidx.ui.text.AnnotatedString component1();
+ method public androidx.ui.core.OffsetMap component2();
+ method public androidx.ui.core.TransformedText copy(androidx.ui.text.AnnotatedString transformedText, androidx.ui.core.OffsetMap offsetMap);
+ method public androidx.ui.core.OffsetMap getOffsetMap();
+ method public androidx.ui.text.AnnotatedString getTransformedText();
+ }
+
+ public interface VisualTransformation {
+ method public androidx.ui.core.TransformedText filter(androidx.ui.text.AnnotatedString text);
+ }
+
+ public final class VisualTransformationKt {
+ ctor public VisualTransformationKt();
+ }
+
public final class WrapperKt {
ctor public WrapperKt();
method public static void ComposeView(kotlin.jvm.functions.Function0<kotlin.Unit> children);
diff --git a/ui/ui-framework/api/current.txt b/ui/ui-framework/api/current.txt
index cbd3f86..b53ffe0 100644
--- a/ui/ui-framework/api/current.txt
+++ b/ui/ui-framework/api/current.txt
@@ -34,7 +34,7 @@
public final class InputFieldKt {
ctor public InputFieldKt();
- method public static void InputField(androidx.ui.input.EditorState value, androidx.ui.core.EditorStyle editorStyle, androidx.ui.input.KeyboardType keyboardType = KeyboardType.Text, androidx.ui.input.ImeAction imeAction = ImeAction.Unspecified, kotlin.jvm.functions.Function1<? super androidx.ui.input.EditorState,kotlin.Unit> onValueChange = {}, kotlin.jvm.functions.Function1<? super androidx.ui.input.ImeAction,kotlin.Unit> onImeActionPerformed = {});
+ method public static void InputField(androidx.ui.input.EditorState value, androidx.ui.core.EditorStyle editorStyle, androidx.ui.input.KeyboardType keyboardType = KeyboardType.Text, androidx.ui.input.ImeAction imeAction = ImeAction.Unspecified, kotlin.jvm.functions.Function1<? super androidx.ui.input.EditorState,kotlin.Unit> onValueChange = {}, kotlin.jvm.functions.Function1<? super androidx.ui.input.ImeAction,kotlin.Unit> onImeActionPerformed = {}, androidx.ui.core.VisualTransformation? visualTransformation = null);
}
public final class IntrinsicMeasurementsReceiver implements androidx.ui.core.DensityReceiver {
@@ -92,6 +92,11 @@
property public abstract Object? parentData;
}
+ public interface OffsetMap {
+ method public int originalToTransformed(int offset);
+ method public int transformedToOriginal(int offset);
+ }
+
public final class OpacityKt {
ctor public OpacityKt();
method public static void Opacity(@FloatRange(from=0.0, to=1.0) float opacity, kotlin.jvm.functions.Function0<kotlin.Unit> children);
@@ -102,6 +107,13 @@
method public static void ParentData(Object data, kotlin.jvm.functions.Function0<kotlin.Unit> children);
}
+ public final class PasswordVisualTransformation implements androidx.ui.core.VisualTransformation {
+ ctor public PasswordVisualTransformation(char mask);
+ ctor public PasswordVisualTransformation();
+ method public androidx.ui.core.TransformedText filter(androidx.ui.text.AnnotatedString text);
+ method public char getMask();
+ }
+
public abstract class Placeable {
ctor public Placeable();
method public abstract androidx.ui.core.IntPx getHeight();
@@ -174,6 +186,23 @@
method public androidx.ui.core.TextSpanComposition getComposer();
}
+ public final class TransformedText {
+ ctor public TransformedText(androidx.ui.text.AnnotatedString transformedText, androidx.ui.core.OffsetMap offsetMap);
+ method public androidx.ui.text.AnnotatedString component1();
+ method public androidx.ui.core.OffsetMap component2();
+ method public androidx.ui.core.TransformedText copy(androidx.ui.text.AnnotatedString transformedText, androidx.ui.core.OffsetMap offsetMap);
+ method public androidx.ui.core.OffsetMap getOffsetMap();
+ method public androidx.ui.text.AnnotatedString getTransformedText();
+ }
+
+ public interface VisualTransformation {
+ method public androidx.ui.core.TransformedText filter(androidx.ui.text.AnnotatedString text);
+ }
+
+ public final class VisualTransformationKt {
+ ctor public VisualTransformationKt();
+ }
+
public final class WrapperKt {
ctor public WrapperKt();
method public static void ComposeView(kotlin.jvm.functions.Function0<kotlin.Unit> children);
diff --git a/ui/ui-framework/api/restricted_1.0.0-alpha01.txt b/ui/ui-framework/api/restricted_1.0.0-alpha01.txt
index cbd3f86..b53ffe0 100644
--- a/ui/ui-framework/api/restricted_1.0.0-alpha01.txt
+++ b/ui/ui-framework/api/restricted_1.0.0-alpha01.txt
@@ -34,7 +34,7 @@
public final class InputFieldKt {
ctor public InputFieldKt();
- method public static void InputField(androidx.ui.input.EditorState value, androidx.ui.core.EditorStyle editorStyle, androidx.ui.input.KeyboardType keyboardType = KeyboardType.Text, androidx.ui.input.ImeAction imeAction = ImeAction.Unspecified, kotlin.jvm.functions.Function1<? super androidx.ui.input.EditorState,kotlin.Unit> onValueChange = {}, kotlin.jvm.functions.Function1<? super androidx.ui.input.ImeAction,kotlin.Unit> onImeActionPerformed = {});
+ method public static void InputField(androidx.ui.input.EditorState value, androidx.ui.core.EditorStyle editorStyle, androidx.ui.input.KeyboardType keyboardType = KeyboardType.Text, androidx.ui.input.ImeAction imeAction = ImeAction.Unspecified, kotlin.jvm.functions.Function1<? super androidx.ui.input.EditorState,kotlin.Unit> onValueChange = {}, kotlin.jvm.functions.Function1<? super androidx.ui.input.ImeAction,kotlin.Unit> onImeActionPerformed = {}, androidx.ui.core.VisualTransformation? visualTransformation = null);
}
public final class IntrinsicMeasurementsReceiver implements androidx.ui.core.DensityReceiver {
@@ -92,6 +92,11 @@
property public abstract Object? parentData;
}
+ public interface OffsetMap {
+ method public int originalToTransformed(int offset);
+ method public int transformedToOriginal(int offset);
+ }
+
public final class OpacityKt {
ctor public OpacityKt();
method public static void Opacity(@FloatRange(from=0.0, to=1.0) float opacity, kotlin.jvm.functions.Function0<kotlin.Unit> children);
@@ -102,6 +107,13 @@
method public static void ParentData(Object data, kotlin.jvm.functions.Function0<kotlin.Unit> children);
}
+ public final class PasswordVisualTransformation implements androidx.ui.core.VisualTransformation {
+ ctor public PasswordVisualTransformation(char mask);
+ ctor public PasswordVisualTransformation();
+ method public androidx.ui.core.TransformedText filter(androidx.ui.text.AnnotatedString text);
+ method public char getMask();
+ }
+
public abstract class Placeable {
ctor public Placeable();
method public abstract androidx.ui.core.IntPx getHeight();
@@ -174,6 +186,23 @@
method public androidx.ui.core.TextSpanComposition getComposer();
}
+ public final class TransformedText {
+ ctor public TransformedText(androidx.ui.text.AnnotatedString transformedText, androidx.ui.core.OffsetMap offsetMap);
+ method public androidx.ui.text.AnnotatedString component1();
+ method public androidx.ui.core.OffsetMap component2();
+ method public androidx.ui.core.TransformedText copy(androidx.ui.text.AnnotatedString transformedText, androidx.ui.core.OffsetMap offsetMap);
+ method public androidx.ui.core.OffsetMap getOffsetMap();
+ method public androidx.ui.text.AnnotatedString getTransformedText();
+ }
+
+ public interface VisualTransformation {
+ method public androidx.ui.core.TransformedText filter(androidx.ui.text.AnnotatedString text);
+ }
+
+ public final class VisualTransformationKt {
+ ctor public VisualTransformationKt();
+ }
+
public final class WrapperKt {
ctor public WrapperKt();
method public static void ComposeView(kotlin.jvm.functions.Function0<kotlin.Unit> children);
diff --git a/ui/ui-framework/api/restricted_current.txt b/ui/ui-framework/api/restricted_current.txt
index cbd3f86..b53ffe0 100644
--- a/ui/ui-framework/api/restricted_current.txt
+++ b/ui/ui-framework/api/restricted_current.txt
@@ -34,7 +34,7 @@
public final class InputFieldKt {
ctor public InputFieldKt();
- method public static void InputField(androidx.ui.input.EditorState value, androidx.ui.core.EditorStyle editorStyle, androidx.ui.input.KeyboardType keyboardType = KeyboardType.Text, androidx.ui.input.ImeAction imeAction = ImeAction.Unspecified, kotlin.jvm.functions.Function1<? super androidx.ui.input.EditorState,kotlin.Unit> onValueChange = {}, kotlin.jvm.functions.Function1<? super androidx.ui.input.ImeAction,kotlin.Unit> onImeActionPerformed = {});
+ method public static void InputField(androidx.ui.input.EditorState value, androidx.ui.core.EditorStyle editorStyle, androidx.ui.input.KeyboardType keyboardType = KeyboardType.Text, androidx.ui.input.ImeAction imeAction = ImeAction.Unspecified, kotlin.jvm.functions.Function1<? super androidx.ui.input.EditorState,kotlin.Unit> onValueChange = {}, kotlin.jvm.functions.Function1<? super androidx.ui.input.ImeAction,kotlin.Unit> onImeActionPerformed = {}, androidx.ui.core.VisualTransformation? visualTransformation = null);
}
public final class IntrinsicMeasurementsReceiver implements androidx.ui.core.DensityReceiver {
@@ -92,6 +92,11 @@
property public abstract Object? parentData;
}
+ public interface OffsetMap {
+ method public int originalToTransformed(int offset);
+ method public int transformedToOriginal(int offset);
+ }
+
public final class OpacityKt {
ctor public OpacityKt();
method public static void Opacity(@FloatRange(from=0.0, to=1.0) float opacity, kotlin.jvm.functions.Function0<kotlin.Unit> children);
@@ -102,6 +107,13 @@
method public static void ParentData(Object data, kotlin.jvm.functions.Function0<kotlin.Unit> children);
}
+ public final class PasswordVisualTransformation implements androidx.ui.core.VisualTransformation {
+ ctor public PasswordVisualTransformation(char mask);
+ ctor public PasswordVisualTransformation();
+ method public androidx.ui.core.TransformedText filter(androidx.ui.text.AnnotatedString text);
+ method public char getMask();
+ }
+
public abstract class Placeable {
ctor public Placeable();
method public abstract androidx.ui.core.IntPx getHeight();
@@ -174,6 +186,23 @@
method public androidx.ui.core.TextSpanComposition getComposer();
}
+ public final class TransformedText {
+ ctor public TransformedText(androidx.ui.text.AnnotatedString transformedText, androidx.ui.core.OffsetMap offsetMap);
+ method public androidx.ui.text.AnnotatedString component1();
+ method public androidx.ui.core.OffsetMap component2();
+ method public androidx.ui.core.TransformedText copy(androidx.ui.text.AnnotatedString transformedText, androidx.ui.core.OffsetMap offsetMap);
+ method public androidx.ui.core.OffsetMap getOffsetMap();
+ method public androidx.ui.text.AnnotatedString getTransformedText();
+ }
+
+ public interface VisualTransformation {
+ method public androidx.ui.core.TransformedText filter(androidx.ui.text.AnnotatedString text);
+ }
+
+ public final class VisualTransformationKt {
+ ctor public VisualTransformationKt();
+ }
+
public final class WrapperKt {
ctor public WrapperKt();
method public static void ComposeView(kotlin.jvm.functions.Function0<kotlin.Unit> children);
diff --git a/ui/ui-framework/src/main/java/androidx/ui/core/InputField.kt b/ui/ui-framework/src/main/java/androidx/ui/core/InputField.kt
index f834987..b9bb9e9 100644
--- a/ui/ui-framework/src/main/java/androidx/ui/core/InputField.kt
+++ b/ui/ui-framework/src/main/java/androidx/ui/core/InputField.kt
@@ -31,7 +31,6 @@
import androidx.ui.input.EditorState
import androidx.ui.input.ImeAction
import androidx.ui.input.KeyboardType
-import androidx.ui.text.AnnotatedString
import androidx.ui.text.TextPainter
import androidx.ui.text.TextStyle
@@ -100,7 +99,12 @@
onValueChange: (EditorState) -> Unit = {},
/** Called when the InputMethod requested an IME action */
- onImeActionPerformed: (ImeAction) -> Unit = {} // TODO(nona): Define argument type
+ onImeActionPerformed: (ImeAction) -> Unit = {},
+
+ /**
+ * Optional visual filter for changing visual output of input field.
+ */
+ visualTransformation: VisualTransformation? = null
) {
// Ambients
val style = +ambient(CurrentTextStyleAmbient)
@@ -111,10 +115,13 @@
// Memos
val processor = +memo { EditProcessor() }
val mergedStyle = style.merge(editorStyle.textStyle)
- val textPainter = +memo(value, mergedStyle, density, resourceLoader) {
+ val (visualText, offsetMap) = +memo(value, visualTransformation) {
+ InputFieldDelegate.applyVisualFilter(value, visualTransformation)
+ }
+ val textPainter = +memo(visualText, mergedStyle, density, resourceLoader) {
// TODO(nona): Add parameter for text direction, softwrap, etc.
TextPainter(
- text = AnnotatedString(text = value.text),
+ text = visualText,
style = mergedStyle,
density = density,
resourceLoader = resourceLoader
@@ -145,7 +152,8 @@
textPainter,
coords,
textInputService,
- hasFocus.value
+ hasFocus.value,
+ offsetMap
)
}
}
@@ -158,7 +166,14 @@
onValueChange)
},
onDragAt = { InputFieldDelegate.onDragAt(it) },
- onRelease = { InputFieldDelegate.onRelease(it, textPainter, processor, onValueChange) }
+ onRelease = {
+ InputFieldDelegate.onRelease(
+ it,
+ textPainter,
+ processor,
+ offsetMap,
+ onValueChange)
+ }
) {
Layout(
children = @Composable {
@@ -172,13 +187,15 @@
textPainter,
it,
textInputService,
- hasFocus.value
+ hasFocus.value,
+ offsetMap
)
}
}
Draw { canvas, _ -> InputFieldDelegate.draw(
canvas,
value,
+ offsetMap,
textPainter,
hasFocus.value,
editorStyle) }
diff --git a/ui/ui-framework/src/main/java/androidx/ui/core/InputFieldDelegate.kt b/ui/ui-framework/src/main/java/androidx/ui/core/InputFieldDelegate.kt
index a6552d5..907545b 100644
--- a/ui/ui-framework/src/main/java/androidx/ui/core/InputFieldDelegate.kt
+++ b/ui/ui-framework/src/main/java/androidx/ui/core/InputFieldDelegate.kt
@@ -72,6 +72,7 @@
*
* @param canvas The target canvas.
* @param value The editor state
+ * @param offsetMap The offset map
* @param textPainter The text painter
* @param hasFocus true if this widget is focused, otherwise false
* @param editorStyle The editor style.
@@ -80,26 +81,28 @@
fun draw(
canvas: Canvas,
value: EditorState,
+ offsetMap: OffsetMap,
textPainter: TextPainter,
hasFocus: Boolean,
editorStyle: EditorStyle
) {
value.composition?.let {
textPainter.paintBackground(
- it.start,
- it.end,
+ offsetMap.originalToTransformed(it.start),
+ offsetMap.originalToTransformed(it.end),
editorStyle.compositionColor,
canvas
)
}
if (value.selection.collapsed) {
if (hasFocus) {
- textPainter.paintCursor(value.selection.start, canvas)
+ textPainter.paintCursor(
+ offsetMap.originalToTransformed(value.selection.start), canvas)
}
} else {
textPainter.paintBackground(
- value.selection.start,
- value.selection.end,
+ offsetMap.originalToTransformed(value.selection.start),
+ offsetMap.originalToTransformed(value.selection.end),
editorStyle.selectionColor,
canvas
)
@@ -118,16 +121,17 @@
textPainter: TextPainter,
layoutCoordinates: LayoutCoordinates,
textInputService: TextInputService,
- hasFocus: Boolean
+ hasFocus: Boolean,
+ offsetMap: OffsetMap
) {
if (!hasFocus) {
return
}
val bbox = if (value.selection.end < value.text.length) {
- textPainter.getBoundingBox(value.selection.end)
+ textPainter.getBoundingBox(offsetMap.originalToTransformed(value.selection.end))
} else if (value.selection.end != 0) {
- textPainter.getBoundingBox(value.selection.end - 1)
+ textPainter.getBoundingBox(offsetMap.originalToTransformed(value.selection.end) - 1)
} else {
Rect(0f, 0f, 1.0f, textPainter.preferredLineHeight)
}
@@ -186,6 +190,7 @@
* @param position The event position in widget coordinate.
* @param textPainter The text painter
* @param editProcessor The edit processor
+ * @param offsetMap The offset map
* @param onValueChange The callback called when the new editor state arrives.
*/
@JvmStatic
@@ -193,9 +198,10 @@
position: PxPosition,
textPainter: TextPainter,
editProcessor: EditProcessor,
+ offsetMap: OffsetMap,
onValueChange: (EditorState) -> Unit
) {
- val offset = textPainter.getOffsetForPosition(position)
+ val offset = offsetMap.transformedToOriginal(textPainter.getOffsetForPosition(position))
onEditCommand(listOf(SetSelectionEditOp(offset, offset)), editProcessor, onValueChange)
}
@@ -207,7 +213,7 @@
* @param editProcessor The edit processor
* @param keyboardType The keyboard type
* @param onValueChange The callback called when the new editor state arrives.
- * @param onEditorActionPerformed The callback called when the editor action arrives.
+ * @param onImeActionPerformed The callback called when the editor action arrives.
*/
@JvmStatic
fun onFocus(
@@ -243,5 +249,21 @@
onEditCommand(listOf(FinishComposingTextEditOp()), editProcessor, onValueChange)
textInputService?.stopInput()
}
+
+ /**
+ * Helper function of applying visual transformation method to the EditorState.
+ *
+ * @param value An editor state
+ * @param visualTransformation A visual transformation
+ */
+ @JvmStatic
+ fun applyVisualFilter(
+ value: EditorState,
+ visualTransformation: VisualTransformation?
+ ): TransformedText {
+ val annotatedString = AnnotatedString(value.text)
+ return visualTransformation?.filter(annotatedString)
+ ?: TransformedText(annotatedString, identityOffsetMap)
+ }
}
}
diff --git a/ui/ui-framework/src/main/java/androidx/ui/core/VisualTransformation.kt b/ui/ui-framework/src/main/java/androidx/ui/core/VisualTransformation.kt
new file mode 100644
index 0000000..23ff8a6
--- /dev/null
+++ b/ui/ui-framework/src/main/java/androidx/ui/core/VisualTransformation.kt
@@ -0,0 +1,142 @@
+/*
+ * Copyright 2019 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.
+ */
+
+package androidx.ui.core
+
+import androidx.ui.text.AnnotatedString
+
+/**
+ * The map interface used for bidirectional offset mapping from original to transformed text.
+ */
+interface OffsetMap {
+ /**
+ * Convert offset in original text into the offset in transformed text.
+ *
+ * This function must be a monotonically non-decreasing function. In other words, if a cursor
+ * advances in the original text, the cursor in the transformed text must advance or stay there.
+ *
+ * @param offset offset in original text.
+ * @return offset in transformed text
+ * @see VisualTransformation
+ */
+ fun originalToTransformed(offset: Int): Int
+
+ /**
+ * Convert offset in transformed text into the offset in original text.
+ *
+ * This function must be a monotonically non-decreasing function. In other words, if a cursor
+ * advances in the transformed text, the cusrsor in the original text must advance or stay
+ * there.
+ *
+ * @param offset offset in transformed text
+ * @return offset in original text
+ * @see VisualTransformation
+ */
+ fun transformedToOriginal(offset: Int): Int
+}
+
+/**
+ * The transformed text with offset offset mapping
+ */
+data class TransformedText(
+ /**
+ * The transformed text
+ */
+ val transformedText: AnnotatedString,
+
+ /**
+ * The map used for bidirectional offset mapping from original to transformed text.
+ */
+ val offsetMap: OffsetMap
+)
+
+/**
+ * Interface used for changing visual output of the input field.
+ *
+ * This interface can be used for changing visual output of the text in the input field.
+ * For example, you can mask characters in password filed with asterisk with
+ * PasswordVisualTransformation.
+ */
+interface VisualTransformation {
+ /**
+ * Change the visual output of given text.
+ *
+ * Note that the returned text length can be different length from the given text. The widget
+ * will call the offset translator for converting offsets for various reasons, cursor drawing
+ * position, text selection by gesture, etc.
+ *
+ * Example: Credit Card Visual Output (inserting hyphens each 4 digits)
+ * original text : 1234567890123456
+ * transformed text: 1234-5678-9012-3456
+ *
+ * Then, the offset translator should ignore the hyphen characters, so conversion from
+ * original offset to transformed text works like
+ * - The 4th char of the original text is 5th char in the transformed text.
+ * - The 13th char of the original text is 15th char in the transformed text.
+ * Similarly, the reverse conversion works like
+ * - The 5th char of the transformed text is 4th char in the original text.
+ * - The 12th char of the transformed text is 10th char in the original text.
+ *
+ * The reference implementation would be like as follows:
+ * <pre>
+ * val creditCardOffsetTranslator = object : OffsetMap {
+ * override fun originalToTransformed(originalOffset: Int): Int {
+ * if (originalOffset <= 3) return originalOffset
+ * if (originalOffset <= 7) return originalOffset + 1
+ * if (originalOffset <= 11) return originalOffset + 2
+ * if (originalOffset <= 16) return originalOffset + 3
+ * return 19
+ * }
+ *
+ * override fun transformedToOriginal(transformedOffset: Int): Int {
+ * if (transformedOffset <= 4) return transformedOffset
+ * if (transformedOffset <= 9) return transformedOffset - 1
+ * if (transformedOffset <= 14) return transformedOffset - 2
+ * if (transformedOffset <= 19) return transformedOffset - 3
+ * return 16
+ * }
+ * }
+ * </pre>
+ *
+ * TODO(nona): Add paragraph direction argument for determining offset conversion.
+ *
+ * @param text The original text
+ * @return the pair of filtered text and offset translator.
+ */
+ fun filter(text: AnnotatedString): TransformedText
+}
+
+/**
+ * The Visual Filter can be used for password Input Field.
+ *
+ * Note that this visual filter only works for ASCII characters.
+ *
+ * @param mask The mask character used instead of original text.
+ */
+class PasswordVisualTransformation(val mask: Char = '\u2022') : VisualTransformation {
+ override fun filter(text: AnnotatedString): TransformedText {
+ return TransformedText(AnnotatedString(Character.toString(mask).repeat(text.text.length)),
+ identityOffsetMap)
+ }
+}
+
+/**
+ * The offset map used for identity mapping.
+ */
+internal val identityOffsetMap = object : OffsetMap {
+ override fun originalToTransformed(offset: Int): Int = offset
+ override fun transformedToOriginal(offset: Int): Int = offset
+}
diff --git a/ui/ui-framework/src/test/java/androidx/ui/core/InputFieldDelegateTest.kt b/ui/ui-framework/src/test/java/androidx/ui/core/InputFieldDelegateTest.kt
index 8877c86..8f9ccfa 100644
--- a/ui/ui-framework/src/test/java/androidx/ui/core/InputFieldDelegateTest.kt
+++ b/ui/ui-framework/src/test/java/androidx/ui/core/InputFieldDelegateTest.kt
@@ -59,6 +59,37 @@
private lateinit var textInputService: TextInputService
private lateinit var layoutCoordinates: LayoutCoordinates
+ val creditCardOffsetTranslator = object : OffsetMap {
+ override fun originalToTransformed(offset: Int): Int {
+ if (offset <= 3) return offset
+ if (offset <= 7) return offset + 1
+ if (offset <= 11) return offset + 2
+ if (offset <= 16) return offset + 3
+ return 19
+ }
+
+ override fun transformedToOriginal(offset: Int): Int {
+ if (offset <= 4) return offset
+ if (offset <= 9) return offset - 1
+ if (offset <= 14) return offset - 2
+ if (offset <= 19) return offset - 3
+ return 16
+ }
+ }
+
+ private val identityOffsetMap = object : OffsetMap {
+ override fun originalToTransformed(offset: Int): Int = offset
+ override fun transformedToOriginal(offset: Int): Int = offset
+ }
+
+ /**
+ * Test implementation of offset map which doubles the offset in transformed text.
+ */
+ private val skippingOffsetMap = object : OffsetMap {
+ override fun originalToTransformed(offset: Int): Int = offset * 2
+ override fun transformedToOriginal(offset: Int): Int = offset / 2
+ }
+
@Before
fun setup() {
painter = mock()
@@ -80,7 +111,9 @@
textPainter = painter,
value = EditorState(text = "Hello, World", selection = selection),
editorStyle = EditorStyle(selectionColor = selectionColor),
- hasFocus = true)
+ hasFocus = true,
+ offsetMap = identityOffsetMap
+ )
verify(painter, times(1)).paintBackground(
eq(selection.start), eq(selection.end), eq(selectionColor), eq(canvas))
@@ -98,7 +131,9 @@
textPainter = painter,
value = EditorState(text = "Hello, World", selection = cursor),
editorStyle = EditorStyle(),
- hasFocus = true)
+ hasFocus = true,
+ offsetMap = identityOffsetMap
+ )
verify(painter, times(1)).paintCursor(eq(cursor.start), eq(canvas))
verify(painter, times(1)).paint(eq(canvas))
@@ -114,7 +149,9 @@
textPainter = painter,
value = EditorState(text = "Hello, World", selection = cursor),
editorStyle = EditorStyle(),
- hasFocus = false)
+ hasFocus = false,
+ offsetMap = identityOffsetMap
+ )
verify(painter, never()).paintCursor(any(), any())
verify(painter, times(1)).paint(eq(canvas))
@@ -134,7 +171,9 @@
value = EditorState(text = "Hello, World", selection = cursor,
composition = composition),
editorStyle = EditorStyle(compositionColor = compositionColor),
- hasFocus = true)
+ hasFocus = true,
+ offsetMap = identityOffsetMap
+ )
verify(painter, times(1)).paintBackground(
eq(composition.start), eq(composition.end), eq(compositionColor), eq(canvas))
@@ -166,7 +205,8 @@
whenever(processor.onEditCommands(captor.capture())).thenReturn(dummyEditorState)
- InputFieldDelegate.onRelease(position, painter, processor, onValueChange)
+ InputFieldDelegate.onRelease(position, painter, processor,
+ identityOffsetMap, onValueChange)
assertEquals(1, captor.allValues.size)
assertEquals(1, captor.firstValue.size)
@@ -186,8 +226,9 @@
composition = TextRange(1, 3)
),
editorStyle = EditorStyle(compositionColor = Color.Red),
- hasFocus = true
- )
+ hasFocus = true,
+ offsetMap = identityOffsetMap
+ )
inOrder(painter) {
verify(painter).paintBackground(eq(1), eq(3), eq(Color.Red), eq(canvas))
@@ -241,7 +282,9 @@
painter,
layoutCoordinates,
textInputService,
- true /* hasFocus */)
+ true /* hasFocus */,
+ identityOffsetMap
+ )
verify(textInputService).notifyFocusedRect(any())
}
@@ -253,7 +296,9 @@
painter,
layoutCoordinates,
textInputService,
- false /* hasFocus */)
+ false /* hasFocus */,
+ identityOffsetMap
+ )
verify(textInputService, never()).notifyFocusedRect(any())
}
@@ -269,7 +314,9 @@
painter,
layoutCoordinates,
textInputService,
- true /* hasFocus */)
+ true /* hasFocus */,
+ identityOffsetMap
+ )
verify(textInputService).notifyFocusedRect(any())
}
@@ -285,7 +332,8 @@
painter,
layoutCoordinates,
textInputService,
- true /* hasFocus */)
+ true, /* hasFocus */
+ identityOffsetMap)
val captor = argumentCaptor<Rect>()
verify(textInputService).notifyFocusedRect(captor.capture())
assertEquals(dummyHeight, captor.firstValue.height)
@@ -313,4 +361,86 @@
verify(painter, times(1)).layout(constraints)
}
+
+ @Test
+ fun check_draw_uses_offset_map() {
+ val selection = TextRange(1, 3)
+ val selectionColor = Color.Blue
+
+ InputFieldDelegate.draw(
+ canvas = canvas,
+ textPainter = painter,
+ value = EditorState(text = "Hello, World", selection = selection),
+ editorStyle = EditorStyle(selectionColor = selectionColor),
+ hasFocus = true,
+ offsetMap = skippingOffsetMap
+ )
+
+ val selectionStartInTransformedText = selection.start * 2
+ val selectionEmdInTransformedText = selection.end * 2
+
+ verify(painter, times(1)).paintBackground(
+ eq(selectionStartInTransformedText),
+ eq(selectionEmdInTransformedText),
+ eq(selectionColor),
+ eq(canvas))
+ }
+
+ @Test
+ fun check_notify_rect_uses_offset_map() {
+ val dummyRect = Rect(0f, 1f, 2f, 3f)
+ val dummyPoint = PxPosition(5.px, 6.px)
+ val dummyEditorState = EditorState(text = "Hello, World", selection = TextRange(1, 3))
+ whenever(painter.getBoundingBox(any())).thenReturn(dummyRect)
+ whenever(layoutCoordinates.localToRoot(any())).thenReturn(dummyPoint)
+
+ InputFieldDelegate.notifyFocusedRect(
+ dummyEditorState,
+ painter,
+ layoutCoordinates,
+ textInputService,
+ true /* hasFocus */,
+ skippingOffsetMap
+ )
+ verify(painter).getBoundingBox(6)
+ verify(textInputService).notifyFocusedRect(any())
+ }
+
+ @Test
+ fun check_on_release_uses_offset_map() {
+ val position = PxPosition(100.px, 200.px)
+ val offset = 10
+ val dummyEditorState = EditorState(text = "Hello, World", selection = TextRange(1, 1))
+
+ whenever(painter.getOffsetForPosition(position)).thenReturn(offset)
+
+ val captor = argumentCaptor<List<EditOperation>>()
+
+ whenever(processor.onEditCommands(captor.capture())).thenReturn(dummyEditorState)
+
+ InputFieldDelegate.onRelease(position, painter, processor, skippingOffsetMap, onValueChange)
+
+ val cursorOffsetInTransformedText = offset / 2
+ assertEquals(1, captor.allValues.size)
+ assertEquals(1, captor.firstValue.size)
+ assertTrue(captor.firstValue[0] is SetSelectionEditOp)
+ val setSelectionEditOp = captor.firstValue[0] as SetSelectionEditOp
+ assertEquals(cursorOffsetInTransformedText, setSelectionEditOp.start)
+ assertEquals(cursorOffsetInTransformedText, setSelectionEditOp.end)
+ verify(onValueChange, times(1)).invoke(eq(dummyEditorState))
+ }
+
+ @Test
+ fun use_identity_mapping_if_visual_transformation_is_null() {
+ val (visualText, offsetMap) = InputFieldDelegate.applyVisualFilter(
+ EditorState(text = "Hello, World"),
+ null)
+
+ assertEquals("Hello, World", visualText.text)
+ for (i in 0..visualText.text.length) {
+ // Identity mapping returns if no visual filter is provided.
+ assertEquals(i, offsetMap.originalToTransformed(i))
+ assertEquals(i, offsetMap.transformedToOriginal(i))
+ }
+ }
}
diff --git a/ui/ui-framework/src/test/java/androidx/ui/core/PasswordVisualTransformationTest.kt b/ui/ui-framework/src/test/java/androidx/ui/core/PasswordVisualTransformationTest.kt
new file mode 100644
index 0000000..1284040
--- /dev/null
+++ b/ui/ui-framework/src/test/java/androidx/ui/core/PasswordVisualTransformationTest.kt
@@ -0,0 +1,52 @@
+/*
+ * Copyright 2019 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.
+ */
+
+package androidx.ui.core
+
+import androidx.ui.text.AnnotatedString
+import org.junit.Assert.assertEquals
+import org.junit.Test
+import org.junit.runner.RunWith
+import org.junit.runners.JUnit4
+
+@RunWith(JUnit4::class)
+class PasswordVisualTransformationTest {
+ @Test
+ fun check_visual_output_is_masked_with_asterisk() {
+ val transformation = PasswordVisualTransformation(mask = '*')
+ val text = AnnotatedString("12345")
+ val (transformedText, map) = transformation.filter(text)
+
+ assertEquals("*****", transformedText.text)
+ for (i in 0..transformedText.text.length) {
+ assertEquals(i, map.originalToTransformed(i))
+ assertEquals(i, map.transformedToOriginal(i))
+ }
+ }
+
+ @Test
+ fun check_visual_output_is_masked_with_default() {
+ val filter = PasswordVisualTransformation()
+ val text = AnnotatedString("1234567890")
+ val (filtered, map) = filter.filter(text)
+
+ assertEquals("\u2022".repeat(10), filtered.text)
+ for (i in 0..filtered.text.length) {
+ assertEquals(i, map.originalToTransformed(i))
+ assertEquals(i, map.transformedToOriginal(i))
+ }
+ }
+}
\ No newline at end of file
diff --git a/ui/ui-text/integration-tests/text-demos/src/main/AndroidManifest.xml b/ui/ui-text/integration-tests/text-demos/src/main/AndroidManifest.xml
index 77e5d1b..66c0d73 100644
--- a/ui/ui-text/integration-tests/text-demos/src/main/AndroidManifest.xml
+++ b/ui/ui-text/integration-tests/text-demos/src/main/AndroidManifest.xml
@@ -30,7 +30,16 @@
<activity android:name=".CraneInputFieldActivity"
android:configChanges="orientation|screenSize"
- android:label="Text/Input Field Demo">
+ android:label="Text/Input Field/Input Field Demo">
+ <intent-filter>
+ <action android:name="android.intent.action.MAIN" />
+ <category android:name="androidx.ui.demos.SAMPLE_CODE" />
+ </intent-filter>
+ </activity>
+
+ <activity android:name=".CraneVariousInputFieldActivity"
+ android:configChanges="orientation|screenSize"
+ android:label="Text/Input Field/Various Input Field Demo">
<intent-filter>
<action android:name="android.intent.action.MAIN" />
<category android:name="androidx.ui.demos.SAMPLE_CODE" />
diff --git a/ui/ui-text/integration-tests/text-demos/src/main/java/androidx/ui/text/demos/CraneVariousInputField.kt b/ui/ui-text/integration-tests/text-demos/src/main/java/androidx/ui/text/demos/CraneVariousInputField.kt
new file mode 100644
index 0000000..1f46d90
--- /dev/null
+++ b/ui/ui-text/integration-tests/text-demos/src/main/java/androidx/ui/text/demos/CraneVariousInputField.kt
@@ -0,0 +1,243 @@
+/*
+ * Copyright 2019 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.
+ */
+
+package androidx.ui.text.demos
+
+import androidx.compose.composer
+import androidx.compose.Composable
+import androidx.compose.state
+import androidx.compose.unaryPlus
+import androidx.ui.core.EditorStyle
+import androidx.ui.core.InputField
+import androidx.ui.core.OffsetMap
+import androidx.ui.core.PasswordVisualTransformation
+import androidx.ui.core.TransformedText
+import androidx.ui.core.VisualTransformation
+import androidx.ui.input.EditorState
+import androidx.ui.input.ImeAction
+import androidx.ui.input.KeyboardType
+import androidx.ui.layout.Column
+import androidx.ui.layout.CrossAxisAlignment
+import androidx.ui.layout.VerticalScroller
+import androidx.ui.text.AnnotatedString
+import androidx.ui.text.TextStyle
+import java.util.Locale
+
+/**
+ * The offset translater used for credit card input field.
+ *
+ * @see creditCardFilter
+ */
+private val creditCardOffsetTranslator = object : OffsetMap {
+ override fun originalToTransformed(offset: Int): Int {
+ if (offset <= 3) return offset
+ if (offset <= 7) return offset + 1
+ if (offset <= 11) return offset + 2
+ if (offset <= 16) return offset + 3
+ return 19
+ }
+
+ override fun transformedToOriginal(offset: Int): Int {
+ if (offset <= 4) return offset
+ if (offset <= 9) return offset - 1
+ if (offset <= 14) return offset - 2
+ if (offset <= 19) return offset - 3
+ return 16
+ }
+}
+
+/**
+ * The visual filter for credit card input field.
+ *
+ * This filter converts up to 16 digits to hyphen connected 4 digits string.
+ * For example, "1234567890123456" will be shown as "1234-5678-9012-3456".
+ */
+private val creditCardFilter = object : VisualTransformation {
+ override fun filter(text: AnnotatedString): TransformedText {
+ val trimmed = if (text.text.length >= 16) text.text.substring(0..15) else text.text
+ var out = ""
+ for (i in 0 until trimmed.length) {
+ out += trimmed[i]
+ if (i % 4 == 3 && i != 15) out += "-"
+ }
+ return TransformedText(AnnotatedString(out), creditCardOffsetTranslator)
+ }
+}
+
+/**
+ * The offset translator which works for all offset keep remains the same.
+ */
+private val identityTranslater = object : OffsetMap {
+ override fun originalToTransformed(offset: Int): Int = offset
+ override fun transformedToOriginal(offset: Int): Int = offset
+}
+
+/**
+ * The visual filter for capitalization.
+ *
+ * This filer converts ASCII characters to capital form.
+ */
+private class CapitalizeTransformation(val locale: Locale = Locale.US) : VisualTransformation {
+ override fun filter(text: AnnotatedString): TransformedText {
+ // TODO(nona): identityTranslater doesn't work for some locale, e.g. Turkish
+ return TransformedText(AnnotatedString(text.text.toUpperCase(locale)), identityTranslater)
+ }
+}
+
+/**
+ * The offset translator for phone number
+ *
+ * @see phoneNumberFilter
+ */
+private val phoneNumberOffsetTranslater = object : OffsetMap {
+ override fun originalToTransformed(offset: Int): Int {
+ return when (offset) {
+ 0 -> 1
+ 1 -> 2
+ 2 -> 3
+ 3 -> 6
+ 4 -> 7
+ 5 -> 8
+ 6 -> 10
+ 7 -> 11
+ 8 -> 12
+ 9 -> 13
+ else -> 14
+ }
+ }
+
+ override fun transformedToOriginal(offset: Int): Int {
+ return when (offset) {
+ 0 -> 0
+ 1 -> 0
+ 2 -> 1
+ 3 -> 2
+ 4 -> 3
+ 5 -> 3
+ 6 -> 3
+ 7 -> 4
+ 8 -> 5
+ 9 -> 6
+ 10 -> 6
+ 11 -> 7
+ 12 -> 8
+ 13 -> 9
+ else -> 10
+ }
+ }
+}
+
+/**
+ * The visual filter for phone number.
+ *
+ * This filter converts up to 10 digits to phone number form.
+ * For example, "1234567890" will be shown as "(123) 456-7890".
+ */
+private val phoneNumberFilter = object : VisualTransformation {
+ override fun filter(text: AnnotatedString): TransformedText {
+ val trimmed = if (text.text.length >= 10) text.text.substring(0..9) else text.text
+ val filled = trimmed + "_".repeat(10 - trimmed.length)
+ val res = "(" + filled.substring(0..2) + ") " + filled.substring(3..5) + "-" +
+ filled.substring(6..9)
+ return TransformedText(AnnotatedString(text = res), phoneNumberOffsetTranslater)
+ }
+}
+
+private val emailFilter = object : VisualTransformation {
+ override fun filter(text: AnnotatedString): TransformedText {
+ return if (text.text.indexOf("@") == -1) {
+ TransformedText(AnnotatedString(text = text.text + "@gmail.com"), identityTranslater)
+ } else {
+ TransformedText(text, identityTranslater)
+ }
+ }
+}
+
+@Composable
+fun VariousInputFieldDemo() {
+ VerticalScroller {
+ Column(crossAxisAlignment = CrossAxisAlignment.Start) {
+ TagLine(tag = "Capitalization")
+ VariousEditLine(
+ keyboardType = KeyboardType.Ascii,
+ onValueChange = { old, new ->
+ if (new.text.any { !it.isLetterOrDigit() }) old else new
+ },
+ visualTransformation = CapitalizeTransformation()
+ )
+
+ TagLine(tag = "Capitalization (Turkish)")
+ VariousEditLine(
+ keyboardType = KeyboardType.Ascii,
+ onValueChange = { old, new ->
+ if (new.text.any { !it.isLetterOrDigit() }) old else new
+ },
+ visualTransformation = CapitalizeTransformation(Locale.forLanguageTag("tr"))
+ )
+
+ TagLine(tag = "Password")
+ VariousEditLine(
+ keyboardType = KeyboardType.Password,
+ onValueChange = { old, new ->
+ if (new.text.any { !it.isLetterOrDigit() }) old else new
+ },
+ visualTransformation = PasswordVisualTransformation()
+ )
+
+ TagLine(tag = "Phone Number")
+ VariousEditLine(
+ keyboardType = KeyboardType.Number,
+ onValueChange = { old, new ->
+ if (new.text.length > 10 || new.text.any { !it.isDigit() }) old else new
+ },
+ visualTransformation = phoneNumberFilter
+ )
+
+ TagLine(tag = "Credit Card")
+ VariousEditLine(
+ keyboardType = KeyboardType.Number,
+ onValueChange = { old, new ->
+ if (new.text.length > 16 || new.text.any { !it.isDigit() }) old else new
+ },
+ visualTransformation = creditCardFilter
+ )
+
+ TagLine(tag = "Email Suggestion")
+ VariousEditLine(
+ keyboardType = KeyboardType.Email,
+ visualTransformation = emailFilter
+ )
+ }
+ }
+}
+
+@Composable
+fun VariousEditLine(
+ keyboardType: KeyboardType = KeyboardType.Text,
+ imeAction: ImeAction = ImeAction.Unspecified,
+ onValueChange: (EditorState, EditorState) -> EditorState = { _, new -> new },
+ visualTransformation: VisualTransformation
+) {
+ val state = +state { EditorState() }
+ InputField(
+ value = state.value,
+ keyboardType = keyboardType,
+ imeAction = imeAction,
+ visualTransformation = visualTransformation,
+ onValueChange = { state.value = onValueChange(state.value, it) },
+ editorStyle = EditorStyle(textStyle = TextStyle(fontSize = fontSize8))
+ )
+}
diff --git a/ui/ui-text/integration-tests/text-demos/src/main/java/androidx/ui/text/demos/CraneVariousInputFieldActivity.kt b/ui/ui-text/integration-tests/text-demos/src/main/java/androidx/ui/text/demos/CraneVariousInputFieldActivity.kt
new file mode 100644
index 0000000..0aae714
--- /dev/null
+++ b/ui/ui-text/integration-tests/text-demos/src/main/java/androidx/ui/text/demos/CraneVariousInputFieldActivity.kt
@@ -0,0 +1,29 @@
+/*
+ * Copyright 2019 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.
+ */
+
+package androidx.ui.text.demos
+
+import android.app.Activity
+import android.os.Bundle
+import androidx.compose.composer
+import androidx.ui.core.setContent
+
+class CraneVariousInputFieldActivity : Activity() {
+ override fun onCreate(savedInstanceState: Bundle?) {
+ super.onCreate(savedInstanceState)
+ setContent { VariousInputFieldDemo() }
+ }
+}