Use LayoutString in Material3 APIs
This also creates a basicText method to help with using LayoutString in the material library.
Bug:368337914
RelNote: ProtoLayout Material3 API now accepts LayoutString to support both static and dynamic texts.
Change-Id: I9c24a24c4e6b0076ec0256dfdc5302899a0b24a1
diff --git a/wear/protolayout/protolayout-material3/api/current.txt b/wear/protolayout/protolayout-material3/api/current.txt
index dcfc117..7e28703 100644
--- a/wear/protolayout/protolayout-material3/api/current.txt
+++ b/wear/protolayout/protolayout-material3/api/current.txt
@@ -206,7 +206,7 @@
}
public final class TextKt {
- method public static androidx.wear.protolayout.LayoutElementBuilders.LayoutElement text(androidx.wear.protolayout.material3.MaterialScope, androidx.wear.protolayout.TypeBuilders.StringProp text, optional androidx.wear.protolayout.TypeBuilders.StringLayoutConstraint stringLayoutConstraint, optional int typography, optional androidx.wear.protolayout.types.LayoutColor color, optional boolean italic, optional boolean underline, optional boolean scalable, optional int maxLines, optional int multilineAlignment, optional int overflow, optional androidx.wear.protolayout.ModifiersBuilders.Modifiers modifiers);
+ method public static androidx.wear.protolayout.LayoutElementBuilders.LayoutElement text(androidx.wear.protolayout.material3.MaterialScope, androidx.wear.protolayout.types.LayoutString text, optional int typography, optional androidx.wear.protolayout.types.LayoutColor color, optional boolean italic, optional boolean underline, optional boolean scalable, optional int maxLines, optional int multilineAlignment, optional int overflow, optional androidx.wear.protolayout.modifiers.LayoutModifier modifiers);
}
public final class TitleCardDefaults {
diff --git a/wear/protolayout/protolayout-material3/api/restricted_current.txt b/wear/protolayout/protolayout-material3/api/restricted_current.txt
index dcfc117..7e28703 100644
--- a/wear/protolayout/protolayout-material3/api/restricted_current.txt
+++ b/wear/protolayout/protolayout-material3/api/restricted_current.txt
@@ -206,7 +206,7 @@
}
public final class TextKt {
- method public static androidx.wear.protolayout.LayoutElementBuilders.LayoutElement text(androidx.wear.protolayout.material3.MaterialScope, androidx.wear.protolayout.TypeBuilders.StringProp text, optional androidx.wear.protolayout.TypeBuilders.StringLayoutConstraint stringLayoutConstraint, optional int typography, optional androidx.wear.protolayout.types.LayoutColor color, optional boolean italic, optional boolean underline, optional boolean scalable, optional int maxLines, optional int multilineAlignment, optional int overflow, optional androidx.wear.protolayout.ModifiersBuilders.Modifiers modifiers);
+ method public static androidx.wear.protolayout.LayoutElementBuilders.LayoutElement text(androidx.wear.protolayout.material3.MaterialScope, androidx.wear.protolayout.types.LayoutString text, optional int typography, optional androidx.wear.protolayout.types.LayoutColor color, optional boolean italic, optional boolean underline, optional boolean scalable, optional int maxLines, optional int multilineAlignment, optional int overflow, optional androidx.wear.protolayout.modifiers.LayoutModifier modifiers);
}
public final class TitleCardDefaults {
diff --git a/wear/protolayout/protolayout-material3/samples/src/main/java/androidx/wear/protolayout/material3/samples/Material3ComponentsSample.kt b/wear/protolayout/protolayout-material3/samples/src/main/java/androidx/wear/protolayout/material3/samples/Material3ComponentsSample.kt
index 72203ec..f20c349 100644
--- a/wear/protolayout/protolayout-material3/samples/src/main/java/androidx/wear/protolayout/material3/samples/Material3ComponentsSample.kt
+++ b/wear/protolayout/protolayout-material3/samples/src/main/java/androidx/wear/protolayout/material3/samples/Material3ComponentsSample.kt
@@ -24,8 +24,6 @@
import androidx.wear.protolayout.LayoutElementBuilders.LayoutElement
import androidx.wear.protolayout.ModifiersBuilders
import androidx.wear.protolayout.ModifiersBuilders.Clickable
-import androidx.wear.protolayout.TypeBuilders.StringLayoutConstraint
-import androidx.wear.protolayout.TypeBuilders.StringProp
import androidx.wear.protolayout.expression.DynamicBuilders.DynamicString
import androidx.wear.protolayout.material3.AppCardStyle
import androidx.wear.protolayout.material3.CardDefaults.filledTonalCardColors
@@ -40,18 +38,20 @@
import androidx.wear.protolayout.material3.iconEdgeButton
import androidx.wear.protolayout.material3.materialScope
import androidx.wear.protolayout.material3.primaryLayout
-import androidx.wear.protolayout.material3.prop
import androidx.wear.protolayout.material3.text
import androidx.wear.protolayout.material3.textEdgeButton
import androidx.wear.protolayout.material3.titleCard
import androidx.wear.protolayout.modifiers.LayoutModifier
import androidx.wear.protolayout.modifiers.contentDescription
+import androidx.wear.protolayout.types.LayoutString
+import androidx.wear.protolayout.types.asLayoutConstraint
+import androidx.wear.protolayout.types.layoutString
/** Builds Material3 text element with default options. */
@Sampled
fun helloWorldTextDefault(context: Context, deviceConfiguration: DeviceParameters): LayoutElement =
materialScope(context, deviceConfiguration) {
- text(text = "Hello Material3".prop(), typography = Typography.DISPLAY_LARGE)
+ text(text = "Hello Material3".layoutString, typography = Typography.DISPLAY_LARGE)
}
/** Builds Material3 text element with some of the overridden defaults. */
@@ -63,10 +63,11 @@
materialScope(context, deviceConfiguration) {
text(
text =
- StringProp.Builder("Static")
- .setDynamicValue(DynamicString.constant("Dynamic"))
- .build(),
- stringLayoutConstraint = StringLayoutConstraint.Builder("Constraint").build(),
+ LayoutString(
+ "Static",
+ DynamicString.constant("Dynamic"),
+ "LongestConstraint".asLayoutConstraint()
+ ),
typography = Typography.DISPLAY_LARGE,
color = colorScheme.tertiary,
underline = true,
@@ -100,7 +101,7 @@
onClick = clickable,
modifier = LayoutModifier.contentDescription("Description of a button")
) {
- text("Hello".prop())
+ text("Hello".layoutString)
}
}
@@ -112,7 +113,7 @@
): LayoutElement =
materialScope(context, deviceConfiguration) {
primaryLayout(
- titleSlot = { text("App title".prop()) },
+ titleSlot = { text("App title".layoutString) },
mainSlot = {
buttonGroup {
// To be populated with proper components
@@ -158,7 +159,7 @@
height = expand(),
background = { backgroundImage(protoLayoutResourceId = "id") }
) {
- text("Content of the Card!".prop())
+ text("Content of the Card!".layoutString)
}
}
)
@@ -179,9 +180,9 @@
height = expand(),
colors = filledVariantCardColors(),
style = largeTitleCardStyle(),
- title = { text("This is title of the title card".prop()) },
- time = { text("NOW".prop()) },
- content = { text("Content of the Card!".prop()) }
+ title = { text("This is title of the title card".layoutString) },
+ time = { text("NOW".layoutString) },
+ content = { text("Content of the Card!".layoutString) }
)
}
)
@@ -202,10 +203,10 @@
height = expand(),
colors = filledTonalCardColors(),
style = AppCardStyle.largeAppCardStyle(),
- title = { text("This is title of the app card".prop()) },
- time = { text("NOW".prop()) },
- label = { text("Label".prop()) },
- content = { text("Content of the Card!".prop()) },
+ title = { text("This is title of the app card".layoutString) },
+ time = { text("NOW".layoutString) },
+ label = { text("Label".layoutString) },
+ content = { text("Content of the Card!".layoutString) },
)
}
)
diff --git a/wear/protolayout/protolayout-material3/src/androidTest/java/androidx/wear/protolayout/material3/TestCasesGenerator.kt b/wear/protolayout/protolayout-material3/src/androidTest/java/androidx/wear/protolayout/material3/TestCasesGenerator.kt
index cfac80b..ff49447 100644
--- a/wear/protolayout/protolayout-material3/src/androidTest/java/androidx/wear/protolayout/material3/TestCasesGenerator.kt
+++ b/wear/protolayout/protolayout-material3/src/androidTest/java/androidx/wear/protolayout/material3/TestCasesGenerator.kt
@@ -35,6 +35,7 @@
import androidx.wear.protolayout.modifiers.LayoutModifier
import androidx.wear.protolayout.modifiers.contentDescription
import androidx.wear.protolayout.types.LayoutColor
+import androidx.wear.protolayout.types.layoutString
import com.google.common.collect.ImmutableMap
private const val CONTENT_DESCRIPTION_PLACEHOLDER = "Description"
@@ -81,20 +82,20 @@
primaryLayoutWithOverrideIcon(
mainSlot = {
text(
- text = "Text in the main slot that overflows".prop(),
+ text = "Text in the main slot that overflows".layoutString,
color = colorScheme.secondary
)
},
bottomSlot = {
textEdgeButton(
onClick = clickable,
- labelContent = { text("Action".prop()) },
+ labelContent = { text("Action".layoutString) },
modifier =
LayoutModifier.contentDescription(CONTENT_DESCRIPTION_PLACEHOLDER),
colors = filledButtonColors()
)
},
- titleSlot = { text("Title".prop()) },
+ titleSlot = { text("Title".layoutString) },
overrideIcon = true
)
}
@@ -124,7 +125,7 @@
bottomSlot = {
textEdgeButton(
onClick = clickable,
- labelContent = { text("Action that overflows".prop()) },
+ labelContent = { text("Action that overflows".layoutString) },
modifier =
LayoutModifier.contentDescription(CONTENT_DESCRIPTION_PLACEHOLDER),
colors = filledVariantButtonColors()
@@ -149,7 +150,7 @@
background = { backgroundImage(protoLayoutResourceId = IMAGE_ID) }
) {
text(
- "Card with image background".prop(),
+ "Card with image background".layoutString,
color = colorScheme.onBackground
)
}
@@ -163,7 +164,9 @@
colors = filledTonalButtonColors()
)
},
- titleSlot = { text("Title that overflows".prop(), color = colorScheme.error) }
+ titleSlot = {
+ text("Title that overflows".layoutString, color = colorScheme.error)
+ }
)
}
testCases["primarylayout_titlecard_bottomslot_golden$goldenSuffix"] =
@@ -181,16 +184,16 @@
title = {
text(
"Title Card text that will overflow after 2 max lines of text"
- .prop()
+ .layoutString
)
},
- time = { text("Now".prop()) },
- content = { text("Default title card".prop()) },
+ time = { text("Now".layoutString) },
+ content = { text("Default title card".layoutString) },
colors = filledVariantCardColors()
)
},
- bottomSlot = { text("Bottom Slot that overflows".prop()) },
- titleSlot = { text("TitleCard".prop(), color = colorScheme.secondaryDim) }
+ bottomSlot = { text("Bottom Slot that overflows".layoutString) },
+ titleSlot = { text("TitleCard".layoutString, color = colorScheme.secondaryDim) }
)
}
testCases["primarylayout_bottomslot_withlabel_golden$goldenSuffix"] =
@@ -203,9 +206,11 @@
mainSlot = {
coloredBox(color = colorScheme.errorContainer, shape = shapes.extraLarge)
},
- bottomSlot = { text("Bottom Slot".prop()) },
- labelForBottomSlot = { text("Label in bottom slot overflows".prop()) },
- titleSlot = { text("Title".prop(), color = colorScheme.secondaryContainer) }
+ bottomSlot = { text("Bottom Slot".layoutString) },
+ labelForBottomSlot = { text("Label in bottom slot overflows".layoutString) },
+ titleSlot = {
+ text("Title".layoutString, color = colorScheme.secondaryContainer)
+ }
)
}
testCases["primarylayout_nobottomslot_golden$goldenSuffix"] =
@@ -218,8 +223,10 @@
mainSlot = {
coloredBox(color = colorScheme.tertiaryContainer, shape = shapes.extraLarge)
},
- labelForBottomSlot = { text("Ignored Label in bottom slot".prop()) },
- titleSlot = { text("Title".prop(), color = colorScheme.secondaryContainer) }
+ labelForBottomSlot = { text("Ignored Label in bottom slot".layoutString) },
+ titleSlot = {
+ text("Title".layoutString, color = colorScheme.secondaryContainer)
+ }
)
}
testCases["primarylayout_nobottomslotnotitle_golden$NORMAL_SCALE_SUFFIX"] =
diff --git a/wear/protolayout/protolayout-material3/src/main/java/androidx/wear/protolayout/material3/Helpers.kt b/wear/protolayout/protolayout-material3/src/main/java/androidx/wear/protolayout/material3/Helpers.kt
index 5d1ace0..4cf5b75 100644
--- a/wear/protolayout/protolayout-material3/src/main/java/androidx/wear/protolayout/material3/Helpers.kt
+++ b/wear/protolayout/protolayout-material3/src/main/java/androidx/wear/protolayout/material3/Helpers.kt
@@ -22,8 +22,6 @@
import androidx.annotation.Dimension.Companion.DP
import androidx.annotation.Dimension.Companion.SP
import androidx.annotation.FloatRange
-import androidx.annotation.RestrictTo
-import androidx.wear.protolayout.ColorBuilders.argb
import androidx.wear.protolayout.DimensionBuilders.DpProp
import androidx.wear.protolayout.DimensionBuilders.WrappedDimensionProp
import androidx.wear.protolayout.DimensionBuilders.dp
@@ -43,7 +41,6 @@
import androidx.wear.protolayout.ModifiersBuilders.ElementMetadata
import androidx.wear.protolayout.ModifiersBuilders.Modifiers
import androidx.wear.protolayout.ModifiersBuilders.Padding
-import androidx.wear.protolayout.TypeBuilders.StringProp
import androidx.wear.protolayout.materialcore.fontscaling.FontScaleConverterFactory
import androidx.wear.protolayout.types.LayoutColor
import androidx.wear.protolayout.types.argb
@@ -95,9 +92,6 @@
internal fun verticalSpacer(@Dimension(unit = DP) widthDp: Int): Spacer =
Spacer.Builder().setWidth(widthDp.toDp()).setHeight(expand()).build()
-@RestrictTo(RestrictTo.Scope.LIBRARY_GROUP)
-public fun String.prop(): StringProp = StringProp.Builder(this).build()
-
/**
* Returns [wrap] but with minimum dimension of [MINIMUM_TAP_TARGET_SIZE] for accessibility
* requirements of tap targets.
diff --git a/wear/protolayout/protolayout-material3/src/main/java/androidx/wear/protolayout/material3/Text.kt b/wear/protolayout/protolayout-material3/src/main/java/androidx/wear/protolayout/material3/Text.kt
index 5faf4b5..074afc8 100644
--- a/wear/protolayout/protolayout-material3/src/main/java/androidx/wear/protolayout/material3/Text.kt
+++ b/wear/protolayout/protolayout-material3/src/main/java/androidx/wear/protolayout/material3/Text.kt
@@ -17,14 +17,13 @@
package androidx.wear.protolayout.material3
import androidx.wear.protolayout.LayoutElementBuilders.LayoutElement
-import androidx.wear.protolayout.LayoutElementBuilders.Text
import androidx.wear.protolayout.LayoutElementBuilders.TextAlignment
import androidx.wear.protolayout.LayoutElementBuilders.TextOverflow
-import androidx.wear.protolayout.ModifiersBuilders.Modifiers
-import androidx.wear.protolayout.TypeBuilders.StringLayoutConstraint
-import androidx.wear.protolayout.TypeBuilders.StringProp
+import androidx.wear.protolayout.layout.basicText
import androidx.wear.protolayout.material3.Typography.TypographyToken
+import androidx.wear.protolayout.modifiers.LayoutModifier
import androidx.wear.protolayout.types.LayoutColor
+import androidx.wear.protolayout.types.LayoutString
/**
* ProtoLayout component that represents text object holding any information.
@@ -33,8 +32,6 @@
* [Typography].
*
* @param text The text content for this component.
- * @param stringLayoutConstraint The layout constraints used to correctly measure Text view size and
- * align text when `text` has dynamic value.
* @param typography The typography from [Typography] to be applied to this text. This will have
* predefined default value specified by each components that uses this text, to achieve the
* recommended look.
@@ -46,14 +43,12 @@
* @param maxLines The maximum number of lines that text can occupy.
* @param multilineAlignment The horizontal alignment of the multiple lines of text.
* @param overflow The overflow strategy when text doesn't have enough space to be shown.
- * @param modifiers The additional [Modifiers] for this text.
+ * @param modifiers Modifiers to set to this element.
* @sample androidx.wear.protolayout.material3.samples.helloWorldTextDefault
* @sample androidx.wear.protolayout.material3.samples.helloWorldTextDynamicCustom
*/
public fun MaterialScope.text(
- text: StringProp,
- stringLayoutConstraint: StringLayoutConstraint =
- StringLayoutConstraint.Builder(text.value).build(),
+ text: LayoutString,
@TypographyToken typography: Int = defaultTextElementStyle.typography,
color: LayoutColor = defaultTextElementStyle.color,
italic: Boolean = defaultTextElementStyle.italic,
@@ -62,20 +57,18 @@
maxLines: Int = defaultTextElementStyle.maxLines,
@TextAlignment multilineAlignment: Int = defaultTextElementStyle.multilineAlignment,
@TextOverflow overflow: Int = defaultTextElementStyle.overflow,
- modifiers: Modifiers = Modifiers.Builder().build()
+ modifiers: LayoutModifier = LayoutModifier
): LayoutElement =
- Text.Builder()
- .setText(text)
- .setLayoutConstraintsForDynamicText(stringLayoutConstraint)
- .setFontStyle(
+ basicText(
+ text = text,
+ fontStyle =
createFontStyleBuilder(typographyToken = typography, deviceConfiguration, scalable)
.setColor(color.prop)
.setItalic(italic)
.setUnderline(underline)
- .build()
- )
- .setMaxLines(maxLines)
- .setMultilineAlignment(multilineAlignment)
- .setOverflow(overflow)
- .setModifiers(modifiers)
- .build()
+ .build(),
+ maxLines = maxLines,
+ multilineAlignment = multilineAlignment,
+ overflow = overflow,
+ modifier = modifiers
+ )
diff --git a/wear/protolayout/protolayout-material3/src/test/java/androidx/wear/protolayout/material3/CardTest.kt b/wear/protolayout/protolayout-material3/src/test/java/androidx/wear/protolayout/material3/CardTest.kt
index 980bfeb..9d2b8aa 100644
--- a/wear/protolayout/protolayout-material3/src/test/java/androidx/wear/protolayout/material3/CardTest.kt
+++ b/wear/protolayout/protolayout-material3/src/test/java/androidx/wear/protolayout/material3/CardTest.kt
@@ -34,6 +34,7 @@
import androidx.wear.protolayout.testing.hasText
import androidx.wear.protolayout.testing.hasWidth
import androidx.wear.protolayout.types.argb
+import androidx.wear.protolayout.types.layoutString
import org.junit.Test
import org.junit.runner.RunWith
import org.robolectric.annotation.internal.DoNotInstrument
@@ -156,7 +157,7 @@
modifier = LayoutModifier.contentDescription(CONTENT_DESCRIPTION),
background = { backgroundImage(IMAGE_ID) }
) {
- text(TEXT.prop())
+ text(TEXT.layoutString)
}
}
@@ -174,7 +175,7 @@
modifier = LayoutModifier.contentDescription(CONTENT_DESCRIPTION),
backgroundColor = color.argb
) {
- text(TEXT.prop())
+ text(TEXT.layoutString)
}
}
@@ -200,9 +201,9 @@
content = contentColor.argb,
time = timeColor.argb
),
- title = { text(TEXT.prop()) },
- content = { text(TEXT2.prop()) },
- time = { text(TEXT3.prop()) },
+ title = { text(TEXT.layoutString) },
+ content = { text(TEXT2.layoutString) },
+ time = { text(TEXT3.layoutString) },
)
}
@@ -235,10 +236,10 @@
time = timeColor.argb,
label = labelColor.argb
),
- title = { text(TEXT.prop()) },
- content = { text(TEXT2.prop()) },
- time = { text(TEXT3.prop()) },
- label = { text(TEXT4.prop()) },
+ title = { text(TEXT.layoutString) },
+ content = { text(TEXT2.layoutString) },
+ time = { text(TEXT3.layoutString) },
+ label = { text(TEXT4.layoutString) },
)
}
@@ -265,7 +266,7 @@
width = expand(),
height = height.toDp()
) {
- text(TEXT.prop())
+ text(TEXT.layoutString)
}
}
@@ -303,7 +304,7 @@
onClick = CLICKABLE,
modifier = LayoutModifier.contentDescription(CONTENT_DESCRIPTION)
) {
- text(TEXT.prop())
+ text(TEXT.layoutString)
}
}
@@ -312,9 +313,9 @@
titleCard(
onClick = CLICKABLE,
modifier = LayoutModifier.contentDescription(CONTENT_DESCRIPTION),
- title = { text(TEXT.prop()) },
- content = { text(TEXT2.prop()) },
- time = { text(TEXT3.prop()) },
+ title = { text(TEXT.layoutString) },
+ content = { text(TEXT2.layoutString) },
+ time = { text(TEXT3.layoutString) },
)
}
@@ -323,11 +324,11 @@
appCard(
onClick = CLICKABLE,
modifier = LayoutModifier.contentDescription(CONTENT_DESCRIPTION),
- title = { text(TEXT.prop()) },
- content = { text(TEXT2.prop()) },
- time = { text(TEXT3.prop()) },
+ title = { text(TEXT.layoutString) },
+ content = { text(TEXT2.layoutString) },
+ time = { text(TEXT3.layoutString) },
avatar = { avatarImage(AVATAR_ID) },
- label = { text(TEXT4.prop()) }
+ label = { text(TEXT4.layoutString) }
)
}
}
diff --git a/wear/protolayout/protolayout-material3/src/test/java/androidx/wear/protolayout/material3/EdgeButtonTest.kt b/wear/protolayout/protolayout-material3/src/test/java/androidx/wear/protolayout/material3/EdgeButtonTest.kt
index 249520b..7a85fae 100644
--- a/wear/protolayout/protolayout-material3/src/test/java/androidx/wear/protolayout/material3/EdgeButtonTest.kt
+++ b/wear/protolayout/protolayout-material3/src/test/java/androidx/wear/protolayout/material3/EdgeButtonTest.kt
@@ -23,7 +23,6 @@
import androidx.wear.protolayout.DeviceParametersBuilders
import androidx.wear.protolayout.LayoutElementBuilders.Image
import androidx.wear.protolayout.ModifiersBuilders.Clickable
-import androidx.wear.protolayout.TypeBuilders.StringProp
import androidx.wear.protolayout.expression.AppDataKey
import androidx.wear.protolayout.expression.DynamicBuilders.DynamicInt32
import androidx.wear.protolayout.material3.EdgeButtonDefaults.BOTTOM_MARGIN_DP
@@ -39,6 +38,9 @@
import androidx.wear.protolayout.testing.hasText
import androidx.wear.protolayout.testing.hasWidth
import androidx.wear.protolayout.testing.isClickable
+import androidx.wear.protolayout.types.LayoutString
+import androidx.wear.protolayout.types.asLayoutConstraint
+import androidx.wear.protolayout.types.layoutString
import org.junit.Test
import org.junit.runner.RunWith
import org.robolectric.annotation.internal.DoNotInstrument
@@ -123,7 +125,7 @@
onClick = CLICKABLE,
modifier = LayoutModifier.contentDescription(CONTENT_DESCRIPTION)
) {
- text(label.prop())
+ text(label.layoutString)
}
}
@@ -137,9 +139,11 @@
val label = "test text"
val stateKey = AppDataKey<DynamicInt32>("testKey")
val dynamicLabel =
- StringProp.Builder(label)
- .setDynamicValue(DynamicInt32.from(stateKey).times(2).format())
- .build()
+ LayoutString(
+ label,
+ DynamicInt32.from(stateKey).times(2).format(),
+ label.asLayoutConstraint()
+ )
val queryProvider =
LayoutElementAssertionsProvider(
diff --git a/wear/protolayout/protolayout-testing/api/current.txt b/wear/protolayout/protolayout-testing/api/current.txt
index 47ceaab..1503d9d1 100644
--- a/wear/protolayout/protolayout-testing/api/current.txt
+++ b/wear/protolayout/protolayout-testing/api/current.txt
@@ -12,7 +12,7 @@
method public static androidx.wear.protolayout.testing.LayoutElementMatcher hasHeight(androidx.wear.protolayout.DimensionBuilders.ContainerDimension height);
method public static androidx.wear.protolayout.testing.LayoutElementMatcher hasHeight(androidx.wear.protolayout.DimensionBuilders.ProportionalDimensionProp height);
method public static androidx.wear.protolayout.testing.LayoutElementMatcher hasImage(String protolayoutResId);
- method public static androidx.wear.protolayout.testing.LayoutElementMatcher hasText(androidx.wear.protolayout.TypeBuilders.StringProp value);
+ method public static androidx.wear.protolayout.testing.LayoutElementMatcher hasText(androidx.wear.protolayout.types.LayoutString value);
method public static androidx.wear.protolayout.testing.LayoutElementMatcher hasText(String value);
method public static androidx.wear.protolayout.testing.LayoutElementMatcher hasText(String value, optional boolean subString);
method public static androidx.wear.protolayout.testing.LayoutElementMatcher hasText(String value, optional boolean subString, optional boolean ignoreCase);
diff --git a/wear/protolayout/protolayout-testing/api/restricted_current.txt b/wear/protolayout/protolayout-testing/api/restricted_current.txt
index 47ceaab..1503d9d1 100644
--- a/wear/protolayout/protolayout-testing/api/restricted_current.txt
+++ b/wear/protolayout/protolayout-testing/api/restricted_current.txt
@@ -12,7 +12,7 @@
method public static androidx.wear.protolayout.testing.LayoutElementMatcher hasHeight(androidx.wear.protolayout.DimensionBuilders.ContainerDimension height);
method public static androidx.wear.protolayout.testing.LayoutElementMatcher hasHeight(androidx.wear.protolayout.DimensionBuilders.ProportionalDimensionProp height);
method public static androidx.wear.protolayout.testing.LayoutElementMatcher hasImage(String protolayoutResId);
- method public static androidx.wear.protolayout.testing.LayoutElementMatcher hasText(androidx.wear.protolayout.TypeBuilders.StringProp value);
+ method public static androidx.wear.protolayout.testing.LayoutElementMatcher hasText(androidx.wear.protolayout.types.LayoutString value);
method public static androidx.wear.protolayout.testing.LayoutElementMatcher hasText(String value);
method public static androidx.wear.protolayout.testing.LayoutElementMatcher hasText(String value, optional boolean subString);
method public static androidx.wear.protolayout.testing.LayoutElementMatcher hasText(String value, optional boolean subString, optional boolean ignoreCase);
diff --git a/wear/protolayout/protolayout-testing/src/main/java/androidx/wear/protolayout/testing/filters.kt b/wear/protolayout/protolayout-testing/src/main/java/androidx/wear/protolayout/testing/filters.kt
index 2e024cc..8a8a141 100644
--- a/wear/protolayout/protolayout-testing/src/main/java/androidx/wear/protolayout/testing/filters.kt
+++ b/wear/protolayout/protolayout-testing/src/main/java/androidx/wear/protolayout/testing/filters.kt
@@ -33,8 +33,8 @@
import androidx.wear.protolayout.LayoutElementBuilders.Spannable
import androidx.wear.protolayout.LayoutElementBuilders.Text
import androidx.wear.protolayout.ModifiersBuilders.Clickable
-import androidx.wear.protolayout.TypeBuilders.StringProp
import androidx.wear.protolayout.proto.DimensionProto
+import androidx.wear.protolayout.types.LayoutString
/** Returns a [LayoutElementMatcher] which checks whether the element is clickable. */
public fun isClickable(): LayoutElementMatcher =
@@ -89,12 +89,12 @@
/**
* Returns a [LayoutElementMatcher] which checks whether the element's text equals the given value.
*/
-public fun hasText(value: StringProp): LayoutElementMatcher =
+public fun hasText(value: LayoutString): LayoutElementMatcher =
LayoutElementMatcher("Element text = '$value'") {
it is Text &&
// TODO: b/375448507 - Add dynamic data evaluation and compare the current string value
- it.text?.toProto()?.value == value.toProto().value &&
- it.text?.toProto()?.dynamicValue == value.toProto().dynamicValue
+ it.text?.toProto()?.value == value.staticValue &&
+ it.text?.toProto()?.dynamicValue == value.dynamicValue?.toDynamicStringProto()
}
/**
diff --git a/wear/protolayout/protolayout-testing/src/test/java/androidx/wear/protolayout/testing/FiltersTest.kt b/wear/protolayout/protolayout-testing/src/test/java/androidx/wear/protolayout/testing/FiltersTest.kt
index 6ca1a48..ba732a8 100644
--- a/wear/protolayout/protolayout-testing/src/test/java/androidx/wear/protolayout/testing/FiltersTest.kt
+++ b/wear/protolayout/protolayout-testing/src/test/java/androidx/wear/protolayout/testing/FiltersTest.kt
@@ -31,16 +31,18 @@
import androidx.wear.protolayout.LayoutElementBuilders.Image
import androidx.wear.protolayout.LayoutElementBuilders.Row
import androidx.wear.protolayout.LayoutElementBuilders.Spacer
-import androidx.wear.protolayout.LayoutElementBuilders.Text
import androidx.wear.protolayout.ModifiersBuilders.Background
import androidx.wear.protolayout.ModifiersBuilders.Clickable
import androidx.wear.protolayout.ModifiersBuilders.ElementMetadata
import androidx.wear.protolayout.ModifiersBuilders.Modifiers
import androidx.wear.protolayout.ModifiersBuilders.Semantics
import androidx.wear.protolayout.StateBuilders
-import androidx.wear.protolayout.TypeBuilders.StringLayoutConstraint
import androidx.wear.protolayout.TypeBuilders.StringProp
import androidx.wear.protolayout.expression.DynamicBuilders
+import androidx.wear.protolayout.layout.basicText
+import androidx.wear.protolayout.types.LayoutString
+import androidx.wear.protolayout.types.asLayoutConstraint
+import androidx.wear.protolayout.types.layoutString
import com.google.common.truth.Truth.assertThat
import org.junit.Test
import org.junit.runner.RunWith
@@ -142,7 +144,7 @@
@Test
fun hasText() {
val textContent = "random test content"
- val testElement = Text.Builder().setText(textContent).build()
+ val testElement = basicText(textContent.layoutString)
assertThat(hasText(textContent).matches(testElement)).isTrue()
assertThat(hasText("blabla").matches(testElement)).isFalse()
@@ -151,16 +153,12 @@
@Test
fun hasDynamicText() {
val textContent =
- StringProp.Builder("static content")
- .setDynamicValue(DynamicBuilders.DynamicString.constant("dynamic content"))
- .build()
- val testElement =
- Text.Builder()
- .setText(textContent)
- .setLayoutConstraintsForDynamicText(
- StringLayoutConstraint.Builder("static content").build()
- )
- .build()
+ LayoutString(
+ "static content",
+ DynamicBuilders.DynamicString.constant("dynamic content"),
+ "static content".asLayoutConstraint()
+ )
+ val testElement = basicText(textContent)
assertThat(hasText(textContent).matches(testElement)).isTrue()
assertThat(hasText("blabla").matches(testElement)).isFalse()
@@ -197,12 +195,11 @@
@Test
fun hasColor_onTextStyle() {
val testText =
- Text.Builder()
- .setText("text")
- .setFontStyle(
+ basicText(
+ "text".layoutString,
+ fontStyle =
FontStyle.Builder().setColor(ColorProp.Builder(Color.CYAN).build()).build()
- )
- .build()
+ )
assertThat(hasColor(Color.CYAN).matches(testText)).isTrue()
assertThat(hasColor(Color.GREEN).matches(testText)).isFalse()
@@ -340,7 +337,7 @@
.addContent(
Column.Builder()
.setWidth(width)
- .addContent(Text.Builder().setText("text").build())
+ .addContent(basicText("text".layoutString))
.build()
)
.build()
@@ -370,7 +367,7 @@
.addContent(
Column.Builder()
.setWidth(width)
- .addContent(Text.Builder().setText("text").build())
+ .addContent(basicText("text".layoutString))
.build()
)
.build()
diff --git a/wear/protolayout/protolayout/api/current.txt b/wear/protolayout/protolayout/api/current.txt
index f18af53..73620fa 100644
--- a/wear/protolayout/protolayout/api/current.txt
+++ b/wear/protolayout/protolayout/api/current.txt
@@ -1481,6 +1481,15 @@
}
+package androidx.wear.protolayout.layout {
+
+ public final class TextKt {
+ method public static androidx.wear.protolayout.LayoutElementBuilders.Text basicText(androidx.wear.protolayout.types.LayoutString text, optional androidx.wear.protolayout.LayoutElementBuilders.FontStyle? fontStyle, optional androidx.wear.protolayout.modifiers.LayoutModifier? modifier, optional int maxLines, optional int multilineAlignment, optional int overflow, optional @Dimension(unit=androidx.annotation.Dimension.Companion.SP) float lineHeight);
+ method public static androidx.wear.protolayout.LayoutElementBuilders.FontStyle fontStyle(optional @Dimension(unit=androidx.annotation.Dimension.Companion.SP) float size, optional boolean italic, optional boolean underline, optional androidx.wear.protolayout.types.LayoutColor? color, optional int weight, optional float letterSpacingEm, optional @androidx.wear.protolayout.expression.RequiresSchemaVersion(major=1, minor=300) java.util.List<java.lang.Float> additionalSizesSp, optional @androidx.wear.protolayout.expression.RequiresSchemaVersion(major=1, minor=400) java.util.List<? extends androidx.wear.protolayout.LayoutElementBuilders.FontSetting> settings, optional @androidx.wear.protolayout.expression.RequiresSchemaVersion(major=1, minor=400) java.util.List<java.lang.String> preferredFontFamilies);
+ }
+
+}
+
package androidx.wear.protolayout.modifiers {
public interface LayoutModifier {
diff --git a/wear/protolayout/protolayout/api/restricted_current.txt b/wear/protolayout/protolayout/api/restricted_current.txt
index f18af53..73620fa 100644
--- a/wear/protolayout/protolayout/api/restricted_current.txt
+++ b/wear/protolayout/protolayout/api/restricted_current.txt
@@ -1481,6 +1481,15 @@
}
+package androidx.wear.protolayout.layout {
+
+ public final class TextKt {
+ method public static androidx.wear.protolayout.LayoutElementBuilders.Text basicText(androidx.wear.protolayout.types.LayoutString text, optional androidx.wear.protolayout.LayoutElementBuilders.FontStyle? fontStyle, optional androidx.wear.protolayout.modifiers.LayoutModifier? modifier, optional int maxLines, optional int multilineAlignment, optional int overflow, optional @Dimension(unit=androidx.annotation.Dimension.Companion.SP) float lineHeight);
+ method public static androidx.wear.protolayout.LayoutElementBuilders.FontStyle fontStyle(optional @Dimension(unit=androidx.annotation.Dimension.Companion.SP) float size, optional boolean italic, optional boolean underline, optional androidx.wear.protolayout.types.LayoutColor? color, optional int weight, optional float letterSpacingEm, optional @androidx.wear.protolayout.expression.RequiresSchemaVersion(major=1, minor=300) java.util.List<java.lang.Float> additionalSizesSp, optional @androidx.wear.protolayout.expression.RequiresSchemaVersion(major=1, minor=400) java.util.List<? extends androidx.wear.protolayout.LayoutElementBuilders.FontSetting> settings, optional @androidx.wear.protolayout.expression.RequiresSchemaVersion(major=1, minor=400) java.util.List<java.lang.String> preferredFontFamilies);
+ }
+
+}
+
package androidx.wear.protolayout.modifiers {
public interface LayoutModifier {
diff --git a/wear/protolayout/protolayout/src/main/java/androidx/wear/protolayout/layout/Text.kt b/wear/protolayout/protolayout/src/main/java/androidx/wear/protolayout/layout/Text.kt
new file mode 100644
index 0000000..45b96f7
--- /dev/null
+++ b/wear/protolayout/protolayout/src/main/java/androidx/wear/protolayout/layout/Text.kt
@@ -0,0 +1,170 @@
+/*
+ * Copyright 2024 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.wear.protolayout.layout
+
+import android.annotation.SuppressLint
+import androidx.annotation.Dimension
+import androidx.annotation.Dimension.Companion.SP
+import androidx.annotation.OptIn
+import androidx.wear.protolayout.LayoutElementBuilders.FONT_WEIGHT_UNDEFINED
+import androidx.wear.protolayout.LayoutElementBuilders.FontSetting
+import androidx.wear.protolayout.LayoutElementBuilders.FontStyle
+import androidx.wear.protolayout.LayoutElementBuilders.FontWeight
+import androidx.wear.protolayout.LayoutElementBuilders.TEXT_ALIGN_UNDEFINED
+import androidx.wear.protolayout.LayoutElementBuilders.TEXT_OVERFLOW_UNDEFINED
+import androidx.wear.protolayout.LayoutElementBuilders.Text
+import androidx.wear.protolayout.LayoutElementBuilders.TextAlignment
+import androidx.wear.protolayout.LayoutElementBuilders.TextOverflow
+import androidx.wear.protolayout.expression.ProtoLayoutExperimental
+import androidx.wear.protolayout.expression.RequiresSchemaVersion
+import androidx.wear.protolayout.modifiers.LayoutModifier
+import androidx.wear.protolayout.modifiers.toProtoLayoutModifiers
+import androidx.wear.protolayout.types.LayoutColor
+import androidx.wear.protolayout.types.LayoutString
+import androidx.wear.protolayout.types.em
+import androidx.wear.protolayout.types.sp
+import java.util.stream.Collectors.toList
+import java.util.stream.Stream
+
+/**
+ * Builds a text string.
+ *
+ * @param text The text to render.
+ * @param fontStyle The style of font to use (size, bold etc). If not specified, defaults to the
+ * platform's default body font.
+ * @param modifier Modifiers to set to this element..
+ * @param maxLines The maximum number of lines that can be represented by the [Text] element. If not
+ * defined, the [Text] element will be treated as a single-line element.
+ * @param multilineAlignment Alignment of the text within its bounds. Note that a [Text] element
+ * will size itself to wrap its contents, so this option is meaningless for single-line text (for
+ * that, use alignment of the outer container). For multi-line text, however, this will set the
+ * alignment of lines relative to the [Text] element bounds. If not defined, defaults to
+ * TEXT_ALIGN_CENTER.
+ * @param overflow How to handle text which overflows the bound of the [Text] element. A [Text]
+ * element will grow as large as possible inside its parent container (while still respecting
+ * max_lines); if it cannot grow large enough to render all of its text, the text which cannot fit
+ * inside its container will be truncated. If not defined, defaults to TEXT_OVERFLOW_TRUNCATE.
+ * @param lineHeight The explicit height between lines of text. This is equivalent to the vertical
+ * distance between subsequent baselines. If not specified, defaults the font's recommended
+ * interline spacing.
+ */
+@SuppressLint("ProtoLayoutMinSchema")
+@Suppress("MissingJvmstatic") // Kotlin-friendly version of already available Java Apis
+fun basicText(
+ text: LayoutString,
+ fontStyle: FontStyle? = null,
+ modifier: LayoutModifier? = null,
+ maxLines: Int = 0,
+ @TextAlignment multilineAlignment: Int = TEXT_ALIGN_UNDEFINED,
+ @TextOverflow overflow: Int = TEXT_OVERFLOW_UNDEFINED,
+ @Dimension(SP) lineHeight: Float = Float.NaN,
+) =
+ Text.Builder()
+ .setText(text.prop)
+ .apply {
+ text.layoutConstraint?.let { setLayoutConstraintsForDynamicText(it) }
+ fontStyle?.let { setFontStyle(it) }
+ modifier?.let { setModifiers(it.toProtoLayoutModifiers()) }
+ if (maxLines != 0) {
+ setMaxLines(maxLines)
+ }
+ if (multilineAlignment != TEXT_ALIGN_UNDEFINED) {
+ setMultilineAlignment(multilineAlignment)
+ }
+ if (overflow != TEXT_OVERFLOW_UNDEFINED) {
+ setOverflow(overflow)
+ }
+ if (!lineHeight.isNaN()) {
+ setLineHeight(lineHeight.sp)
+ }
+ }
+ .build()
+
+/**
+ * Builds the styling of a font (e.g. font size, and metrics).
+ *
+ * @param size The size of the font, in scaled pixels (sp). If not specified, defaults to the size
+ * of the system's "body" font.
+ * @param italic Whether the text should be rendered in a italic typeface.
+ * @param underline Whether the text should be rendered with an underline.
+ * @param color The text color. If not defined, defaults to white.
+ * @param weight The weight of the font. If the provided value is not supported on a platform, the
+ * nearest supported value will be used. If not defined, or when set to an invalid value, defaults
+ * to "normal".
+ * @param letterSpacingEm The text letter-spacing. Positive numbers increase the space between
+ * letters while negative numbers tighten the space. If not specified, defaults to 0.
+ * @param additionalSizesSp when this [FontStyle] is applied to a [Text] element with static text,
+ * the text size will be automatically picked from the provided sizes to try to perfectly fit
+ * within its parent bounds. In other words, the largest size from the specified preset sizes that
+ * can fit the most text within the parent bounds will be used.
+ * @param settings The collection of font settings to be applied. If more than one Setting with the
+ * same axis tag is specified, the first one will be used. Supported settings depend on the font
+ * used and renderer version.
+ * @param preferredFontFamilies is the ordered list of font families to pick from for this
+ * [FontStyle]. If the given font family is not available on a device, the fallback values will be
+ * attempted to use, in order in which they are given. Note that support for font family
+ * customization is dependent on the target platform.
+ */
+@OptIn(ProtoLayoutExperimental::class)
+@SuppressLint("ProtoLayoutMinSchema")
+@Suppress("MissingJvmstatic") // Kotlin-friendly version of already available Java Apis
+fun fontStyle(
+ @Dimension(SP) size: Float = 0f,
+ italic: Boolean = false,
+ underline: Boolean = false,
+ color: LayoutColor? = null,
+ @FontWeight weight: Int = FONT_WEIGHT_UNDEFINED,
+ letterSpacingEm: Float = Float.NaN,
+ @RequiresSchemaVersion(major = 1, minor = 300) additionalSizesSp: List<Float> = listOf(),
+ @RequiresSchemaVersion(major = 1, minor = 400) settings: List<FontSetting> = listOf(),
+ @RequiresSchemaVersion(major = 1, minor = 400) preferredFontFamilies: List<String> = listOf()
+): FontStyle =
+ FontStyle.Builder()
+ .apply {
+ if (size != 0f) {
+ setSize(size.sp)
+ }
+ setItalic(italic)
+ setUnderline(underline)
+ color?.let { setColor(it.prop) }
+ if (weight != FONT_WEIGHT_UNDEFINED) {
+ setWeight(weight)
+ }
+ if (settings.isNotEmpty()) {
+ setSettings(*settings.toTypedArray())
+ }
+ if (!letterSpacingEm.isNaN()) {
+ setLetterSpacing(letterSpacingEm.em)
+ }
+ if (preferredFontFamilies.isNotEmpty()) {
+ setPreferredFontFamilies(
+ preferredFontFamilies.first(),
+ *preferredFontFamilies.subList(1, preferredFontFamilies.size).toTypedArray()
+ )
+ }
+ if (additionalSizesSp.isNotEmpty()) {
+ setSizes(
+ *Stream.concat(
+ if (size != 0f) Stream.of(size.toInt()) else Stream.empty(),
+ additionalSizesSp.stream().map { it.toInt() }
+ )
+ .collect(toList())
+ .toIntArray()
+ )
+ }
+ }
+ .build()
diff --git a/wear/protolayout/protolayout/src/main/java/androidx/wear/protolayout/types/Helpers.kt b/wear/protolayout/protolayout/src/main/java/androidx/wear/protolayout/types/Helpers.kt
new file mode 100644
index 0000000..2f4295e
--- /dev/null
+++ b/wear/protolayout/protolayout/src/main/java/androidx/wear/protolayout/types/Helpers.kt
@@ -0,0 +1,30 @@
+/*
+ * Copyright 2024 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.wear.protolayout.types
+
+import androidx.wear.protolayout.DimensionBuilders.EmProp
+import androidx.wear.protolayout.DimensionBuilders.SpProp
+import androidx.wear.protolayout.TypeBuilders.BoolProp
+
+internal val Float.sp: SpProp
+ get() = SpProp.Builder().setValue(this).build()
+
+internal val Float.em: EmProp
+ get() = EmProp.Builder().setValue(this).build()
+
+internal val Boolean.prop: BoolProp
+ get() = BoolProp.Builder(this).build()
diff --git a/wear/tiles/tiles-samples/src/main/java/androidx/wear/tiles/samples/tile/PlaygroundTileService.kt b/wear/tiles/tiles-samples/src/main/java/androidx/wear/tiles/samples/tile/PlaygroundTileService.kt
index 4a2eb07..b4eb6a6 100644
--- a/wear/tiles/tiles-samples/src/main/java/androidx/wear/tiles/samples/tile/PlaygroundTileService.kt
+++ b/wear/tiles/tiles-samples/src/main/java/androidx/wear/tiles/samples/tile/PlaygroundTileService.kt
@@ -27,11 +27,11 @@
import androidx.wear.protolayout.material3.avatarImage
import androidx.wear.protolayout.material3.materialScope
import androidx.wear.protolayout.material3.primaryLayout
-import androidx.wear.protolayout.material3.prop
import androidx.wear.protolayout.material3.text
import androidx.wear.protolayout.material3.textEdgeButton
import androidx.wear.protolayout.modifiers.LayoutModifier
import androidx.wear.protolayout.modifiers.contentDescription
+import androidx.wear.protolayout.types.layoutString
import androidx.wear.tiles.RequestBuilders
import androidx.wear.tiles.TileBuilders
import androidx.wear.tiles.TileService
@@ -101,25 +101,25 @@
),
title = {
text(
- "Title Card!".prop(),
+ "Title Card!".layoutString,
maxLines = 1,
)
},
content = {
text(
- "Content of this Card!".prop(),
+ "Content of this Card!".layoutString,
maxLines = 1,
)
},
label = {
text(
- "Hello and welcome Tiles in AndroidX!".prop(),
+ "Hello and welcome Tiles in AndroidX!".layoutString,
)
},
avatar = { avatarImage("id") },
time = {
text(
- "NOW".prop(),
+ "NOW".layoutString,
)
}
)
@@ -129,7 +129,7 @@
onClick = EMPTY_LOAD_CLICKABLE,
modifier = LayoutModifier.contentDescription("EdgeButton"),
) {
- text("Edge".prop())
+ text("Edge".layoutString)
}
}
)