Updated Compose BOM to 2026.03.00

commit_hash:2c382f7fc098471c025ac6e4bdd4b55a2da075d3
This commit is contained in:
pkurchatov
2026-03-25 10:23:44 +03:00
parent 15669fb932
commit c113c765ff
32 changed files with 181 additions and 1029 deletions
+2 -6
View File
@@ -602,12 +602,12 @@
"client/android/compose-interop/src/main/java/com/yandex/div/compose/interop/DivViewInterop.kt":"divkit/public/client/android/compose-interop/src/main/java/com/yandex/div/compose/interop/DivViewInterop.kt",
"client/android/compose/build.gradle.kts":"divkit/public/client/android/compose/build.gradle.kts",
"client/android/compose/proguard-rules.pro":"divkit/public/client/android/compose/proguard-rules.pro",
"client/android/compose/src/androidTest/kotlin/com/yandex/div/compose/DivViewPreview.kt":"divkit/public/client/android/compose/src/androidTest/kotlin/com/yandex/div/compose/DivViewPreview.kt",
"client/android/compose/src/main/kotlin/com/yandex/div/compose/DivComposeConfiguration.kt":"divkit/public/client/android/compose/src/main/kotlin/com/yandex/div/compose/DivComposeConfiguration.kt",
"client/android/compose/src/main/kotlin/com/yandex/div/compose/DivContext.kt":"divkit/public/client/android/compose/src/main/kotlin/com/yandex/div/compose/DivContext.kt",
"client/android/compose/src/main/kotlin/com/yandex/div/compose/DivException.kt":"divkit/public/client/android/compose/src/main/kotlin/com/yandex/div/compose/DivException.kt",
"client/android/compose/src/main/kotlin/com/yandex/div/compose/DivReporter.kt":"divkit/public/client/android/compose/src/main/kotlin/com/yandex/div/compose/DivReporter.kt",
"client/android/compose/src/main/kotlin/com/yandex/div/compose/DivView.kt":"divkit/public/client/android/compose/src/main/kotlin/com/yandex/div/compose/DivView.kt",
"client/android/compose/src/main/kotlin/com/yandex/div/compose/DivViewPreview.kt":"divkit/public/client/android/compose/src/main/kotlin/com/yandex/div/compose/DivViewPreview.kt",
"client/android/compose/src/main/kotlin/com/yandex/div/compose/actions/DivActionData.kt":"divkit/public/client/android/compose/src/main/kotlin/com/yandex/div/compose/actions/DivActionData.kt",
"client/android/compose/src/main/kotlin/com/yandex/div/compose/actions/DivActionHandler.kt":"divkit/public/client/android/compose/src/main/kotlin/com/yandex/div/compose/actions/DivActionHandler.kt",
"client/android/compose/src/main/kotlin/com/yandex/div/compose/actions/DivActionHandlingContext.kt":"divkit/public/client/android/compose/src/main/kotlin/com/yandex/div/compose/actions/DivActionHandlingContext.kt",
@@ -1839,20 +1839,16 @@
"client/android/divkit-demo-app/src/androidTest/java/com/yandex/div/steps/VisibilityActionsSteps.kt":"divkit/public/client/android/divkit-demo-app/src/androidTest/java/com/yandex/div/steps/VisibilityActionsSteps.kt",
"client/android/divkit-demo-app/src/androidTest/java/com/yandex/div/utils/CharSequences.kt":"divkit/public/client/android/divkit-demo-app/src/androidTest/java/com/yandex/div/utils/CharSequences.kt",
"client/android/divkit-demo-app/src/androidTest/java/com/yandex/div/utils/OutsideActions.kt":"divkit/public/client/android/divkit-demo-app/src/androidTest/java/com/yandex/div/utils/OutsideActions.kt",
"client/android/divkit-demo-app/src/androidTest/java/com/yandex/div/utils/UiTestDivActionHandler.kt":"divkit/public/client/android/divkit-demo-app/src/androidTest/java/com/yandex/div/utils/UiTestDivActionHandler.kt",
"client/android/divkit-demo-app/src/androidTest/java/com/yandex/div/utils/Utils.kt":"divkit/public/client/android/divkit-demo-app/src/androidTest/java/com/yandex/div/utils/Utils.kt",
"client/android/divkit-demo-app/src/androidTest/java/com/yandex/div/utils/ViewCoordinatesHelper.kt":"divkit/public/client/android/divkit-demo-app/src/androidTest/java/com/yandex/div/utils/ViewCoordinatesHelper.kt",
"client/android/divkit-demo-app/src/androidTest/java/com/yandex/div/utils/ViewInteractionExt.kt":"divkit/public/client/android/divkit-demo-app/src/androidTest/java/com/yandex/div/utils/ViewInteractionExt.kt",
"client/android/divkit-demo-app/src/androidTest/java/com/yandex/div/utils/ViewPagerActions.kt":"divkit/public/client/android/divkit-demo-app/src/androidTest/java/com/yandex/div/utils/ViewPagerActions.kt",
"client/android/divkit-demo-app/src/androidTest/java/com/yandex/div/view/TextView.kt":"divkit/public/client/android/divkit-demo-app/src/androidTest/java/com/yandex/div/view/TextView.kt",
"client/android/divkit-demo-app/src/androidTest/java/com/yandex/div/view/Utils.kt":"divkit/public/client/android/divkit-demo-app/src/androidTest/java/com/yandex/div/view/Utils.kt",
"client/android/divkit-demo-app/src/androidTest/java/com/yandex/div/view/ViewActions.kt":"divkit/public/client/android/divkit-demo-app/src/androidTest/java/com/yandex/div/view/ViewActions.kt",
"client/android/divkit-demo-app/src/androidTest/java/com/yandex/div/view/ViewAssertions.kt":"divkit/public/client/android/divkit-demo-app/src/androidTest/java/com/yandex/div/view/ViewAssertions.kt",
"client/android/divkit-demo-app/src/androidTest/java/com/yandex/div/view/ViewInteraction.kt":"divkit/public/client/android/divkit-demo-app/src/androidTest/java/com/yandex/div/view/ViewInteraction.kt",
"client/android/divkit-demo-app/src/androidTest/java/com/yandex/div/view/ViewMatchers.kt":"divkit/public/client/android/divkit-demo-app/src/androidTest/java/com/yandex/div/view/ViewMatchers.kt",
"client/android/divkit-demo-app/src/androidTest/java/com/yandex/div/view/actions/ExtendedScrollTo.kt":"divkit/public/client/android/divkit-demo-app/src/androidTest/java/com/yandex/div/view/actions/ExtendedScrollTo.kt",
"client/android/divkit-demo-app/src/androidTest/java/com/yandex/div/view/getThemeColor.kt":"divkit/public/client/android/divkit-demo-app/src/androidTest/java/com/yandex/div/view/getThemeColor.kt",
"client/android/divkit-demo-app/src/androidTest/java/com/yandex/div/view/viewHelpers.kt":"divkit/public/client/android/divkit-demo-app/src/androidTest/java/com/yandex/div/view/viewHelpers.kt",
"client/android/divkit-demo-app/src/debug/AndroidManifest.xml":"divkit/public/client/android/divkit-demo-app/src/debug/AndroidManifest.xml",
"client/android/divkit-demo-app/src/main/AndroidManifest.xml":"divkit/public/client/android/divkit-demo-app/src/main/AndroidManifest.xml",
"client/android/divkit-demo-app/src/main/assets/application/demo.json":"divkit/public/client/android/divkit-demo-app/src/main/assets/application/demo.json",
@@ -2007,7 +2003,6 @@
"client/android/divkit-demo-app/src/main/java/com/yandex/divkit/demo/utils/DivkitDemoPermissionHelper.kt":"divkit/public/client/android/divkit-demo-app/src/main/java/com/yandex/divkit/demo/utils/DivkitDemoPermissionHelper.kt",
"client/android/divkit-demo-app/src/main/java/com/yandex/divkit/demo/utils/DivkitDemoUriHandler.kt":"divkit/public/client/android/divkit-demo-app/src/main/java/com/yandex/divkit/demo/utils/DivkitDemoUriHandler.kt",
"client/android/divkit-demo-app/src/main/java/com/yandex/divkit/demo/utils/HttpUtil.kt":"divkit/public/client/android/divkit-demo-app/src/main/java/com/yandex/divkit/demo/utils/HttpUtil.kt",
"client/android/divkit-demo-app/src/main/java/com/yandex/divkit/demo/utils/JsonAssetReader.kt":"divkit/public/client/android/divkit-demo-app/src/main/java/com/yandex/divkit/demo/utils/JsonAssetReader.kt",
"client/android/divkit-demo-app/src/main/java/com/yandex/divkit/demo/utils/MetricaUtils.kt":"divkit/public/client/android/divkit-demo-app/src/main/java/com/yandex/divkit/demo/utils/MetricaUtils.kt",
"client/android/divkit-demo-app/src/main/java/com/yandex/divkit/demo/utils/QRCaptureActivity.kt":"divkit/public/client/android/divkit-demo-app/src/main/java/com/yandex/divkit/demo/utils/QRCaptureActivity.kt",
"client/android/divkit-demo-app/src/main/java/com/yandex/divkit/demo/utils/Toasts.kt":"divkit/public/client/android/divkit-demo-app/src/main/java/com/yandex/divkit/demo/utils/Toasts.kt",
@@ -17187,6 +17182,7 @@
"client/android/test-utils/src/main/kotlin/com/yandex/div/test/crossplatform/ParsingResult.kt":"divkit/public/client/android/test-utils/src/main/kotlin/com/yandex/div/test/crossplatform/ParsingResult.kt",
"client/android/test-utils/src/main/kotlin/com/yandex/div/test/crossplatform/ParsingUtils.kt":"divkit/public/client/android/test-utils/src/main/kotlin/com/yandex/div/test/crossplatform/ParsingUtils.kt",
"client/android/test-utils/src/main/kotlin/com/yandex/div/test/data/DivActionUtils.kt":"divkit/public/client/android/test-utils/src/main/kotlin/com/yandex/div/test/data/DivActionUtils.kt",
"client/android/test-utils/src/main/kotlin/com/yandex/div/test/data/DivBackgroundUtils.kt":"divkit/public/client/android/test-utils/src/main/kotlin/com/yandex/div/test/data/DivBackgroundUtils.kt",
"client/android/test-utils/src/main/kotlin/com/yandex/div/test/data/DivContainerUtils.kt":"divkit/public/client/android/test-utils/src/main/kotlin/com/yandex/div/test/data/DivContainerUtils.kt",
"client/android/test-utils/src/main/kotlin/com/yandex/div/test/data/DivDataUtils.kt":"divkit/public/client/android/test-utils/src/main/kotlin/com/yandex/div/test/data/DivDataUtils.kt",
"client/android/test-utils/src/main/kotlin/com/yandex/div/test/data/DivTextUtils.kt":"divkit/public/client/android/test-utils/src/main/kotlin/com/yandex/div/test/data/DivTextUtils.kt",
@@ -26,10 +26,7 @@ dependencies {
implementation platform(libs.androidx.compose.bom)
implementation libs.androidx.compose.foundation
implementation libs.androidx.compose.ui.tooling.preview
implementation libs.androidx.core
debugImplementation libs.androidx.compose.ui.tooling
}
tasks.withType(KotlinCompile).configureEach {
+7 -1
View File
@@ -9,6 +9,10 @@ apply(from = "../div-tests.gradle")
android {
namespace = "com.yandex.div.compose"
defaultConfig {
minSdk = 23
}
}
dependencies {
@@ -19,7 +23,6 @@ dependencies {
implementation(platform(libs.androidx.compose.bom))
implementation(libs.androidx.compose.material3)
implementation(libs.androidx.compose.ui.tooling.preview)
implementation(libs.androidx.coreKtx)
implementation(libs.coil.compose)
implementation(libs.coil.network)
@@ -29,6 +32,9 @@ dependencies {
debugImplementation(libs.androidx.compose.ui.tooling)
androidTestImplementation(project(":test-utils"))
androidTestImplementation(libs.androidx.compose.ui.tooling.preview)
testImplementation(project(":test-utils"))
testImplementation(libs.androidx.compose.ui.test.junit4)
testImplementation(libs.androidx.compose.ui.test.manifest)
@@ -0,0 +1,62 @@
package com.yandex.div.compose
import androidx.compose.runtime.Composable
import androidx.compose.runtime.CompositionLocalProvider
import androidx.compose.ui.platform.LocalContext
import androidx.compose.ui.tooling.preview.Preview
import com.yandex.div.test.data.action
import com.yandex.div.test.data.color
import com.yandex.div.test.data.constant
import com.yandex.div.test.data.container
import com.yandex.div.test.data.data
import com.yandex.div.test.data.expression
import com.yandex.div.test.data.setVariableAction
import com.yandex.div.test.data.solidBackground
import com.yandex.div.test.data.text
import com.yandex.div.test.data.typedValue
import com.yandex.div.test.data.variable
import com.yandex.div2.DivEdgeInsets
@Preview
@Composable
fun DivViewPreview() {
val divContext = DivComposeConfiguration()
.createContext(baseContext = LocalContext.current)
CompositionLocalProvider(LocalContext provides divContext) {
DivView(data = testData)
}
}
private val testData = data(
content = container(
backgrounds = listOf(
solidBackground(color = color(0xFF90C090))
),
items = listOf(
text(
action = action(
typed = setVariableAction(
name = "title",
value = typedValue("New Title")
)
),
fontSize = 36,
text = expression("@{title}"),
textColor = color(0xFFBF0000)
)
),
margins = DivEdgeInsets(
top = constant(10),
bottom = constant(10)
),
paddings = DivEdgeInsets(
start = constant(10),
end = constant(10),
top = constant(20),
bottom = constant(20)
)
),
variables = listOf(
variable(name = "title", value = "Hello!")
)
)
@@ -1,107 +0,0 @@
package com.yandex.div.compose
import androidx.compose.runtime.Composable
import androidx.compose.runtime.CompositionLocalProvider
import androidx.compose.ui.platform.LocalContext
import androidx.compose.ui.tooling.preview.Preview
import com.yandex.div.internal.parser.TYPE_HELPER_STRING
import com.yandex.div.json.ParsingErrorLogger
import com.yandex.div.json.expressions.Expression
import com.yandex.div2.Div
import com.yandex.div2.DivAction
import com.yandex.div2.DivActionSetVariable
import com.yandex.div2.DivActionTyped
import com.yandex.div2.DivBackground
import com.yandex.div2.DivContainer
import com.yandex.div2.DivData
import com.yandex.div2.DivEdgeInsets
import com.yandex.div2.DivSolidBackground
import com.yandex.div2.DivText
import com.yandex.div2.DivTypedValue
import com.yandex.div2.DivVariable
import com.yandex.div2.StrValue
import com.yandex.div2.StrVariable
@Preview
@Composable
private fun DivViewPreview() {
val divContext = DivComposeConfiguration()
.createContext(baseContext = LocalContext.current)
CompositionLocalProvider(LocalContext provides divContext) {
DivView(data = testData)
}
}
private val testData = DivData(
logId = "preview",
variables = listOf(
DivVariable.Str(
StrVariable(
name = "title",
value = constant("Hello!")
)
)
),
states = listOf(
DivData.State(
stateId = 0,
div = Div.Container(
value = DivContainer(
orientation = constant(DivContainer.Orientation.VERTICAL),
paddings = DivEdgeInsets(
start = constant(10),
end = constant(10),
top = constant(20),
bottom = constant(20)
),
margins = DivEdgeInsets(
top = constant(10),
bottom = constant(10)
),
background = listOf(
DivBackground.Solid(
value = DivSolidBackground(
color = constant(0xFF909090.toInt()),
)
)
),
items = listOf(
Div.Text(
value = DivText(
action = DivAction(
logId = constant("test"),
typed = DivActionTyped.SetVariable(
DivActionSetVariable(
variableName = constant("title"),
value = DivTypedValue.Str(
StrValue(value = constant("New Title"))
)
)
)
),
fontSize = constant(36),
text = expression("@{title}"),
textColor = constant(0xFFBF0000.toInt()),
)
)
)
)
)
)
)
)
private fun <T : Any> constant(value: T): Expression<T> {
return Expression.ConstantExpression(value)
}
private fun expression(expression: String): Expression<String> {
return Expression.MutableExpression<String, String>(
expressionKey = "test",
rawExpression = expression,
converter = null,
validator = { true },
logger = ParsingErrorLogger.ASSERT,
typeHelper = TYPE_HELPER_STRING
)
}
@@ -44,7 +44,7 @@ class DivPreloaderTest {
private val extensionHandlers = listOf<DivExtensionHandler>(mock(), mock())
private val extensionHandlersController = DivExtensionController(extensionHandlers)
private val divText = text(text = constant("test"))
private val divText = text(text = "test")
private val custom = DivCustom(customType = "test")
private val divCustom = Div.Custom(custom)
@@ -4,10 +4,10 @@ import android.net.Uri
import com.yandex.div.core.asExpression
import com.yandex.div.core.mockExpressionResolver
import com.yandex.div.test.data.container
import com.yandex.div.test.data.text
import com.yandex.div2.Div
import com.yandex.div2.DivGallery
import com.yandex.div2.DivImage
import com.yandex.div2.DivText
import org.junit.Assert.assertEquals
import org.junit.Test
import org.junit.runner.RunWith
@@ -20,7 +20,7 @@ class DivWalkTreeTest {
@Test
fun `walking single node hierarchy`() {
val rootDiv = divText("lorem ipsum")
val rootDiv = text(text = "lorem ipsum")
val divWalk = rootDiv.walk(resolver)
.map { item -> item.div.type }
@@ -31,8 +31,8 @@ class DivWalkTreeTest {
@Test
fun `walking multiple node hierarchy`() {
val rootDiv = container(
listOf(
divText("lorem ipsum"),
items = listOf(
text(text = "lorem ipsum"),
divImage("https://none")
)
)
@@ -46,14 +46,14 @@ class DivWalkTreeTest {
@Test
fun `walking uses depth-first order`() {
val rootDiv = container(
listOf(
items = listOf(
container(
listOf(
divText("lorem ipsum"),
items = listOf(
text(text = "lorem ipsum"),
divImage("https://none")
)
),
divText("lorem ipsum")
text(text = "lorem ipsum")
)
)
@@ -66,14 +66,14 @@ class DivWalkTreeTest {
@Test
fun `onEnter excludes subtree from walking`() {
val rootDiv = container(
listOf(
items = listOf(
divGallery(
listOf(
divText("lorem ipsum"),
text(text = "lorem ipsum"),
divImage("https://none")
)
),
divText("lorem ipsum")
text(text = "lorem ipsum")
)
)
@@ -87,11 +87,11 @@ class DivWalkTreeTest {
@Test
fun `maxDepth limits walk depth`() {
val rootDiv = container(
listOf(
divText("lorem ipsum"),
items = listOf(
text(text = "lorem ipsum"),
container(
listOf(
divText("lorem ipsum"),
items = listOf(
text(text = "lorem ipsum"),
divImage("https://none")
)
)
@@ -108,11 +108,11 @@ class DivWalkTreeTest {
@Test
fun `onEnter() called only for branch nodes`() {
val rootDiv = container(
listOf(
divText("lorem ipsum"),
items = listOf(
text(text = "lorem ipsum"),
divGallery(
listOf(
divText("lorem ipsum"),
text(text = "lorem ipsum"),
divImage("https://none")
)
)
@@ -133,11 +133,11 @@ class DivWalkTreeTest {
@Test
fun `onLeave() called only for branch nodes`() {
val rootDiv = container(
listOf(
divText("lorem ipsum"),
items = listOf(
text(text = "lorem ipsum"),
divGallery(
listOf(
divText("lorem ipsum"),
text(text = "lorem ipsum"),
divImage("https://none")
)
)
@@ -152,10 +152,6 @@ class DivWalkTreeTest {
assertEquals(listOf("gallery", "container"), leftDivs)
}
private fun divText(text: String): Div {
return Div.Text(DivText(text = text.asExpression()))
}
private fun divImage(imageUrl: String): Div {
return Div.Image(DivImage(imageUrl = Uri.parse(imageUrl).asExpression()))
}
@@ -66,11 +66,11 @@ class DivVisibilityActionTrackerTest {
)
}.toList()
private val div1 = text(text = constant("test1"), visibilityActions = listOf(action1))
private val div2 = text(text = constant("test2"), visibilityActions = listOf(action2))
private val div3 = text(text = constant("test3"), visibilityActions = lottaActions)
private val div4 = text(text = constant("test4"), visibilityActions = actionsWithThreeDifferentDelays)
private val div5 = text(text = constant("test5"), visibilityActions = listOf(action1), disappearActions = listOf(disappearAction1))
private val div1 = text(text = "test1", visibilityActions = listOf(action1))
private val div2 = text(text = "test2", visibilityActions = listOf(action2))
private val div3 = text(text = "test3", visibilityActions = lottaActions)
private val div4 = text(text = "test4", visibilityActions = actionsWithThreeDifferentDelays)
private val div5 = text(text = "test5", visibilityActions = listOf(action1), disappearActions = listOf(disappearAction1))
private val visibilityActionTracker = DivVisibilityActionTracker(
viewVisibilityCalculator,
@@ -3,9 +3,9 @@ package com.yandex.div.core.view2.animations
import android.net.Uri
import com.yandex.div.core.asExpression
import com.yandex.div.core.mockExpressionResolver
import com.yandex.div.test.data.container
import com.yandex.div2.Div
import com.yandex.div2.DivAppearanceTransition
import com.yandex.div2.DivContainer
import com.yandex.div2.DivCustom
import com.yandex.div2.DivGallery
import com.yandex.div2.DivGrid
@@ -83,48 +83,48 @@ class DivComparatorTest {
@Test
fun `containers with similar hierarchy are replaceable`() {
val div1 = divContainer(items = listOf(divText(text = "Text 01")))
val div2 = divContainer(items = listOf(divText(text = "Text 02")))
val div1 = container(items = listOf(divText(text = "Text 01")))
val div2 = container(items = listOf(divText(text = "Text 02")))
assertTrue(DivComparator.areDivsReplaceable(div1, div2, resolver, resolver))
}
@Test
fun `containers with different child count are not replaceable`() {
val div1 = divContainer(items = listOf(divText(text = "Text 01"), divImage(imageUrl = "https://image")))
val div2 = divContainer(items = listOf(divText(text = "Text 02")))
val div1 = container(items = listOf(divText(text = "Text 01"), divImage(imageUrl = "https://image")))
val div2 = container(items = listOf(divText(text = "Text 02")))
assertFalse(DivComparator.areDivsReplaceable(div1, div2, resolver, resolver))
}
@Test
fun `containers with different child types are not replaceable`() {
val div1 = divContainer(items = listOf(divImage(imageUrl = "https://image")))
val div2 = divContainer(items = listOf(divText(text = "Text")))
val div1 = container(items = listOf(divImage(imageUrl = "https://image")))
val div2 = container(items = listOf(divText(text = "Text")))
assertFalse(DivComparator.areDivsReplaceable(div1, div2, resolver, resolver))
}
@Test
fun `containers with different child order are not replaceable`() {
val div1 = divContainer(items = listOf(divImage(imageUrl = "https://image"), divText(text = "Text 01")))
val div2 = divContainer(items = listOf(divText(text = "Text 02"), divImage(imageUrl = "https://image")))
val div1 = container(items = listOf(divImage(imageUrl = "https://image"), divText(text = "Text 01")))
val div2 = container(items = listOf(divText(text = "Text 02"), divImage(imageUrl = "https://image")))
assertFalse(DivComparator.areDivsReplaceable(div1, div2, resolver, resolver))
}
@Test
fun `containers with different child ids are replaceable`() {
val div1 = divContainer(items = listOf(divText(id = "01", text = "Text 01")))
val div2 = divContainer(items = listOf(divText(id = "02", text = "Text 02")))
val div1 = container(items = listOf(divText(id = "01", text = "Text 01")))
val div2 = container(items = listOf(divText(id = "02", text = "Text 02")))
assertTrue(DivComparator.areDivsReplaceable(div1, div2, resolver, resolver))
}
@Test
fun `containers which children has transitions and different ids are not replaceable`() {
val div1 = divContainer(items = listOf(divText(id = "01", text = "Text 01", transitionIn = transitionMock)))
val div2 = divContainer(items = listOf(divText(id = "02", text = "Text 02", transitionIn = transitionMock)))
val div1 = container(items = listOf(divText(id = "01", text = "Text 01", transitionIn = transitionMock)))
val div2 = container(items = listOf(divText(id = "02", text = "Text 02", transitionIn = transitionMock)))
assertFalse(DivComparator.areDivsReplaceable(div1, div2, resolver, resolver))
}
@@ -235,20 +235,6 @@ class DivComparatorTest {
return Div.Custom(DivCustom(customType = type, transitionIn = transitionIn))
}
private fun divContainer(
orientation: DivContainer.Orientation = DivContainer.Orientation.VERTICAL,
items: List<Div>,
transitionIn: DivAppearanceTransition? = null
): Div {
return Div.Container(
DivContainer(
orientation = orientation.asExpression(),
items = items,
transitionIn = transitionIn
)
)
}
private fun divGrid(items: List<Div>, transitionIn: DivAppearanceTransition? = null): Div {
return Div.Grid(
DivGrid(
@@ -25,6 +25,8 @@ android {
}
defaultConfig {
minSdk = 23
versionCode = divkitVersion.versionCode + divkitVersion.buildNumber
buildConfigField "int", "BUILD_NUMBER", "${divkitVersion.buildNumber}"
buildConfigField "String", "METRICA_API_KEY", JsonOutput.toJson(providers.gradleProperty("metricaApiKey").getOrElse(""))
@@ -1,35 +0,0 @@
package com.yandex.div.utils
import android.net.Uri
import com.yandex.div.core.DivActionHandler
import com.yandex.div.core.DivViewFacade
import com.yandex.div.json.expressions.ExpressionResolver
import com.yandex.div2.DivAction
private const val DIV_UI_TEST_SCHEME = "ui-test"
private const val CLICK_ACTION_HOST = "click"
private const val CLICK_TYPE_PARAM = "type"
private const val CLICK_DESCRIPTION_PARAM = "description"
class UiTestDivActionHandler(
private val onClick: ((clickType: String, description: String) -> Unit)? = null
) : DivActionHandler() {
override fun handleAction(action: DivAction, view: DivViewFacade, resolver: ExpressionResolver): Boolean {
val url = action.url?.evaluate(resolver) ?: return false
if (url.scheme == DIV_UI_TEST_SCHEME) {
handleDemoActionUrl(url, view)
return true
}
return super.handleAction(action, view, resolver)
}
private fun handleDemoActionUrl(url: Uri, view: DivViewFacade) {
if (url.host == CLICK_ACTION_HOST) {
val clickType = url.getQueryParameter(CLICK_TYPE_PARAM)
val description = url.getQueryParameter(CLICK_DESCRIPTION_PARAM)
if (clickType != null && description != null) {
onClick?.invoke(clickType, description)
}
}
}
}
@@ -1,56 +0,0 @@
package com.yandex.div.view
import android.text.Spanned
import android.text.style.ClickableSpan
import android.view.View
import android.widget.EditText
import android.widget.TextView
import androidx.core.text.getSpans
import androidx.test.espresso.UiController
import androidx.test.espresso.ViewAction
import androidx.test.espresso.matcher.ViewMatchers.assertThat
import androidx.test.espresso.matcher.ViewMatchers.isAssignableFrom
import androidx.test.espresso.matcher.ViewMatchers.isDisplayed
import org.hamcrest.Matcher
import org.hamcrest.Matchers.allOf
import org.hamcrest.Matchers.notNullValue
object TextViewActions {
/**
* Clear text in TextView
*/
fun clearText(): ViewAction {
return object : ViewAction {
override fun getConstraints(): Matcher<View> = allOf(isDisplayed(), isAssignableFrom(EditText::class.java))
override fun getDescription(): String = "Delete all text in EditText"
override fun perform(uiController: UiController, view: View) {
uiController.loopMainThreadUntilIdle()
(view as EditText).text.clear()
}
}
}
/**
* Taps a first found link if it is recognized.
*/
fun tapLink(): ViewAction {
return object : ViewAction {
override fun getConstraints(): Matcher<View> = allOf(isDisplayed(), isAssignableFrom(TextView::class.java))
override fun getDescription(): String = "Tap on link inside text"
override fun perform(uiController: UiController, view: View) {
val spannable = (view as TextView).text as? Spanned
assertThat("Unknown text format found in $spannable", spannable, notNullValue())
val span = spannable?.getSpans<ClickableSpan>()?.firstOrNull()
assertThat("No link found in $spannable", spannable, notNullValue())
span?.onClick(view)
}
}
}
}
@@ -2,52 +2,22 @@
package com.yandex.div.view
import android.view.InputDevice
import android.view.MotionEvent
import android.view.View
import androidx.annotation.IdRes
import androidx.test.espresso.Espresso.onView
import androidx.test.espresso.NoMatchingViewException
import androidx.test.espresso.PerformException
import androidx.test.espresso.UiController
import androidx.test.espresso.ViewAction
import androidx.test.espresso.ViewInteraction
import androidx.test.espresso.action.GeneralClickAction
import androidx.test.espresso.action.GeneralLocation
import androidx.test.espresso.action.Press
import androidx.test.espresso.action.Tap
import androidx.test.espresso.action.ViewActions
import androidx.test.espresso.matcher.ViewMatchers.isAssignableFrom
import androidx.test.espresso.matcher.ViewMatchers.isClickable
import androidx.test.espresso.matcher.ViewMatchers.isCompletelyDisplayed
import androidx.test.espresso.matcher.ViewMatchers.isDisplayed
import androidx.test.espresso.matcher.ViewMatchers.isRoot
import androidx.test.espresso.util.HumanReadables
import androidx.test.espresso.util.TreeIterables
import com.yandex.div.view.ViewMatchers.isDisplayedForClicking
import org.hamcrest.Matcher
import org.hamcrest.Matchers.allOf
import java.lang.Thread.sleep
import java.util.concurrent.TimeoutException
object ViewActions {
/**
* Performs click on view with very light constraints. Use only if there is no other way
*/
fun callOnClickAction(): ViewAction {
return object : ViewAction {
override fun getConstraints(): Matcher<View> = allOf(isClickable(), isDisplayed())
override fun getDescription() = "Call view onClick directly"
override fun perform(uiController: UiController, view: View) {
view.callOnClick()
}
}
}
/**
* Performs click ignoring 90% visible area constraint
*/
@@ -64,98 +34,8 @@ object ViewActions {
}
}
/**
* Performs an action on a child view.
*/
fun actionOnChild(@IdRes id: Int, action: ViewAction): ViewAction {
return object : ViewAction {
override fun getConstraints(): Matcher<View> = allOf(isDisplayedForClicking(), isClickable())
override fun getDescription() = "Performs '$action' on a child view with id='$id'"
override fun perform(uiController: UiController, view: View) {
val child = view.findViewById<View>(id)
?: throw PerformException.Builder()
.withActionDescription(this.description)
.withViewDescription(HumanReadables.describe(view))
.withCause(RuntimeException("View with id='$id' not found in '$view'"))
.build()
action.perform(uiController, child)
}
}
}
fun clickAtRight(): ViewAction = ViewActions.actionWithAssertions(
GeneralClickAction(
Tap.SINGLE,
GeneralLocation.CENTER_RIGHT,
Press.FINGER,
InputDevice.SOURCE_UNKNOWN,
MotionEvent.BUTTON_PRIMARY
)
)
const val WAITING_TIMEOUT = 15_000L
const val WAITING_TIMEOUT_PER_TRY = 100L
const val MAIN_THREAD_LOOP_TIME_MS = 50L
/**
* Wait for view is completely displayed and perform action
*/
fun waitForCompletelyDisplayedAndPerformAction(
targetAction: ViewAction? = null,
timeoutInMillis: Long = WAITING_TIMEOUT,
) = waitForViewConditionAndPerformAction<View>(
description = "Waits view to become completely displayed.",
timeoutInMillis = timeoutInMillis,
condition = { view -> isCompletelyDisplayed().matches(view) },
targetAction = targetAction,
)
inline fun <reified ViewType : View> waitForViewConditionAndPerformAction(
description: String,
targetAction: ViewAction? = null,
timeoutInMillis: Long = WAITING_TIMEOUT,
condition: Matcher<ViewType>,
) = waitForViewConditionAndPerformAction(description, targetAction, timeoutInMillis,
{ view: ViewType -> condition.matches(view) }
)
inline fun <reified ViewType : View> waitForViewConditionAndPerformAction(
description: String,
targetAction: ViewAction? = null,
timeoutInMillis: Long = WAITING_TIMEOUT,
crossinline condition: (view: ViewType) -> Boolean,
): ViewAction {
return object : ViewAction {
override fun getConstraints(): Matcher<View> = isAssignableFrom(ViewType::class.java)
override fun getDescription() = description
override fun perform(uiController: UiController, view: View) {
uiController.loopMainThreadUntilIdle()
val startTime = System.currentTimeMillis()
val endTime = startTime + timeoutInMillis
do {
if (condition(view as ViewType)) {
targetAction?.perform(uiController, view)
return
}
uiController.loopMainThreadForAtLeast(MAIN_THREAD_LOOP_TIME_MS)
} while (System.currentTimeMillis() < endTime)
throw PerformException.Builder()
.withActionDescription(this.description)
.withViewDescription(HumanReadables.describe(view))
.withCause(TimeoutException())
.build()
}
}
}
fun waitForView(
viewMatcher: Matcher<View>,
@@ -1,41 +1,15 @@
package com.yandex.div.view
import android.view.View
import androidx.test.espresso.ViewAssertion
import androidx.test.espresso.ViewInteraction
import androidx.test.espresso.assertion.ViewAssertions.matches
import androidx.test.espresso.util.HumanReadables
import com.yandex.div.view.ViewMatchers.positionMatch
import com.yandex.test.util.Report.step
import org.hamcrest.MatcherAssert.assertThat
/**
* Provides assertions for all types of views.
* */
object ViewAssertions {
fun doesNotExistOrGone(): ViewAssertion {
return ViewAssertion { view, _ ->
view?.let {
assertThat(
"View is visible or exists: ${HumanReadables.describe(view)}",
view.visibility == View.GONE || view.visibility == View.INVISIBLE
)
}
}
}
fun doesExistAndVisible(): ViewAssertion {
return ViewAssertion { view, ex ->
view?.let {
assertThat(
"View is invisible: ${HumanReadables.describe(view)}",
view.visibility == View.VISIBLE
)
} ?: throw ViewDoesNotExistsException("View does not exist: ${ex.viewMatcherDescription}")
}
}
fun hasChangedPosition(view: ViewInteraction, lastPosition: FloatArray): Unit =
step("Assert view has changed position") {
checkPosition(shouldMatch = false, view, lastPosition)
@@ -52,5 +26,3 @@ object ViewAssertions {
lastPosition: FloatArray
) = view.check(matches(positionMatch(shouldMatch, lastPosition)))
}
class ViewDoesNotExistsException(message: String) : RuntimeException(message)
@@ -1,37 +1,17 @@
package com.yandex.div.view
import android.view.KeyEvent
import android.view.View
import androidx.recyclerview.widget.RecyclerView.ViewHolder
import androidx.test.espresso.NoMatchingViewException
import androidx.test.espresso.UiController
import androidx.test.espresso.ViewAction
import androidx.test.espresso.ViewInteraction
import androidx.test.espresso.action.GeneralLocation
import androidx.test.espresso.action.GeneralSwipeAction
import androidx.test.espresso.action.Press
import androidx.test.espresso.action.Swipe
import androidx.test.espresso.action.ViewActions
import androidx.test.espresso.action.ViewActions.typeText
import androidx.test.espresso.assertion.PositionAssertions
import androidx.test.espresso.assertion.ViewAssertions.doesNotExist
import androidx.test.espresso.assertion.ViewAssertions.matches
import androidx.test.espresso.contrib.RecyclerViewActions.actionOnItemAtPosition
import androidx.test.espresso.contrib.RecyclerViewActions.scrollToPosition
import androidx.test.espresso.matcher.ViewMatchers
import androidx.test.espresso.matcher.ViewMatchers.isCompletelyDisplayed
import com.yandex.div.view.ViewActions.callOnClickAction
import com.yandex.div.view.ViewActions.clickOnPartlyVisibleView
import com.yandex.div.view.ViewActions.waitForCompletelyDisplayedAndPerformAction
import com.yandex.div.view.ViewAssertions.doesExistAndVisible
import com.yandex.div.view.ViewAssertions.doesNotExistOrGone
import com.yandex.div.view.actions.ExtendedScrollTo
import junit.framework.AssertionFailedError
import org.hamcrest.Matcher
fun ViewInteraction.checkIsDisplayed() = check(matches(ViewMatchers.isDisplayed()))
fun ViewInteraction.checkIsChecked() = check(matches(ViewMatchers.isChecked()))
fun ViewInteraction.checkIsNotChecked() = check(matches(ViewMatchers.isNotChecked()))
fun ViewInteraction.whenDisplayed(): ViewInteraction? {
return try {
@@ -45,42 +25,8 @@ fun ViewInteraction.isDisplayed(): Boolean {
return whenDisplayed() != null
}
fun ViewInteraction.whenChecked(): ViewInteraction? {
return try {
checkIsChecked()
} catch (e: AssertionFailedError) {
null
}
}
fun ViewInteraction.whenMatches(matcher: Matcher<View>): ViewInteraction? {
return try {
check(matches(matcher))
} catch (e: AssertionFailedError) {
null
}
}
fun ViewInteraction.isMatching(matcher: Matcher<View>): Boolean {
return whenMatches(matcher) != null
}
fun ViewInteraction.whenNotChecked(): ViewInteraction? {
return try {
checkIsNotChecked()
} catch (e: AssertionFailedError) {
null
}
}
fun ViewInteraction.checkNotExist() = check(doesNotExist())
fun ViewInteraction.checkIsAbove(matcher: Matcher<View>) = check(
PositionAssertions.isCompletelyAbove(
matcher
)
)
fun ViewInteraction.click() = perform(clickOnPartlyVisibleView())
fun ViewInteraction.tap() = perform(ViewActions.click())
@@ -89,133 +35,6 @@ fun ViewInteraction.scrollTo() = perform(ExtendedScrollTo())
fun ViewInteraction.scrollTo(position: Int) = perform(scrollToPosition<ViewHolder>(position))
fun ViewInteraction.callOnClick() = perform(callOnClickAction())
fun ViewInteraction.clearText() = perform(ViewActions.clearText())
fun ViewInteraction.deleteText() = perform(TextViewActions.clearText())
fun ViewInteraction.inputText(text: String) = perform(typeText(text))
fun ViewInteraction.replaceText(text: String) = perform(ViewActions.replaceText(text))
fun ViewInteraction.clickRecyclerViewItem(index: Int) =
perform(actionOnItemAtPosition<ViewHolder>(index, ViewActions.click()))
fun ViewInteraction.swipeRight() = perform(ViewActions.swipeRight())
fun ViewInteraction.swipeLeft() = perform(ViewActions.swipeLeft())
fun ViewInteraction.swipeUp() = perform(ViewActions.swipeUp())
fun ViewInteraction.swipeDown() = perform(ViewActions.swipeDown())
fun ViewInteraction.waitForCompletelyDisplayedAndCallOnClick() =
perform(waitForCompletelyDisplayedAndPerformAction(callOnClickAction()))
fun ViewInteraction.waitForCompletelyDisplayedAndClick() =
perform(waitForCompletelyDisplayedAndPerformAction(ViewActions.click()))
fun ViewInteraction.waitForCompletelyDisplayedAndLongClick() =
perform(waitForCompletelyDisplayedAndPerformAction(ViewActions.longClick()))
fun ViewInteraction.pressSpace() = perform(ViewActions.pressKey(KeyEvent.KEYCODE_SPACE))
fun ViewInteraction.pressEnter() = perform(ViewActions.pressKey(KeyEvent.KEYCODE_ENTER))
fun ViewInteraction.pressBackButton() = perform(ViewActions.pressBack())
fun ViewInteraction.swipeLeftCenterToRightCenter() = perform(
waitForCompletelyDisplayedAndPerformAction(
GeneralSwipeAction(
Swipe.FAST, GeneralLocation.CENTER_LEFT,
GeneralLocation.CENTER_RIGHT, Press.FINGER
)
)
)
fun ViewInteraction.swipeRightCenterToLeftCenter() = perform(
waitForCompletelyDisplayedAndPerformAction(
GeneralSwipeAction(
Swipe.FAST, GeneralLocation.CENTER_RIGHT,
GeneralLocation.CENTER_LEFT, Press.FINGER
)
)
)
fun ViewInteraction.swipeTopCenterToBottomCenter() = perform(
waitForCompletelyDisplayedAndPerformAction(
GeneralSwipeAction(
Swipe.FAST, GeneralLocation.TOP_CENTER,
GeneralLocation.BOTTOM_CENTER, Press.FINGER
)
)
)
fun ViewInteraction.swipeFromCenterToBottomCenter(swipeSpeed: Swipe = Swipe.SLOW) = perform(
waitForCompletelyDisplayedAndPerformAction(
GeneralSwipeAction(
swipeSpeed,
GeneralLocation.CENTER,
GeneralLocation.BOTTOM_CENTER, Press.FINGER
)
)
)
fun ViewInteraction.swipeFromCenterToTopCenter(swipeSpeed: Swipe = Swipe.SLOW) = perform(
waitForCompletelyDisplayedAndPerformAction(
GeneralSwipeAction(
swipeSpeed,
GeneralLocation.CENTER,
GeneralLocation.TOP_CENTER, Press.FINGER
)
)
)
fun ViewInteraction.waitForCompletelyDisplayed(
timeoutInMillis: Long? = null
): ViewInteraction {
return if (timeoutInMillis == null)
perform(waitForCompletelyDisplayedAndPerformAction())
else perform(
waitForCompletelyDisplayedAndPerformAction(
timeoutInMillis = timeoutInMillis
)
)
}
fun ViewInteraction.waitMainThreadBecomeIdle(): ViewInteraction {
return perform(object : ViewAction {
override fun getDescription(): String =
"Wait while main thread become idle on displayed view"
override fun getConstraints(): Matcher<View> = ViewMatchers.isDisplayed()
override fun perform(uiController: UiController, view: View) =
uiController.loopMainThreadUntilIdle()
})
}
fun ViewInteraction.checkExistsAndVisible() = check(doesExistAndVisible())
fun ViewInteraction.checkExistsAndCompletelyVisible() = check(matches(isCompletelyDisplayed()))
fun ViewInteraction.checkNotExistsOrGone() = check(doesNotExistOrGone())
fun <T : View> ViewInteraction.configure(block: (T) -> Unit): ViewInteraction {
return perform(ViewActions.actionWithAssertions(ConfigureViewAction(block)))
}
private class ConfigureViewAction<T : View>(
private val block: (T) -> Unit
) : ViewAction {
override fun getDescription() = "ConfigureViewAction"
override fun getConstraints(): Matcher<View> = ViewMatchers.isDisplayed()
@Suppress("UNCHECKED_CAST")
override fun perform(uiController: UiController, view: View) {
val receiver = view as? T ?: return
block(receiver)
}
}
@@ -1,43 +0,0 @@
package com.yandex.div.view
import android.content.Context
import android.util.TypedValue
import androidx.annotation.AttrRes
import androidx.annotation.ColorInt
/**
* Returns the color int for the provided theme color attribute.
*
* @throws IllegalArgumentException if the attribute is not set in the current theme.
*/
@ColorInt
fun Context.getThemeColor(@AttrRes attribute: Int): Int {
val typedValue = resolveAttributeOrThrow(attribute)
return typedValue.data
}
fun Context.getThemeBoolean(@AttrRes attribute: Int): Boolean {
return resolveAttributeOrThrow(attribute).data != 0
}
fun Context.getThemeResource(@AttrRes attribute: Int): Int {
return resolveAttributeOrThrow(attribute).resourceId
}
internal fun Context.resolveAttributeOrThrow(@AttrRes attributeResId: Int): TypedValue {
val typedValue = TypedValue()
val isAttrResolved = theme.resolveAttribute(attributeResId, typedValue, true)
if (isAttrResolved) {
return typedValue
}
val errorMessage = ("%1\$s requires a value for the %2\$s attribute to be set in your theme.")
throw java.lang.IllegalArgumentException(
String.format(
errorMessage,
javaClass.canonicalName,
resources.getResourceName(attributeResId),
)
)
}
@@ -1,271 +0,0 @@
package com.yandex.div.view
import android.animation.AnimatorInflater
import android.app.Activity
import android.content.ContextWrapper
import android.graphics.PorterDuff
import android.graphics.PorterDuffColorFilter
import android.os.Build
import android.view.View
import android.view.ViewGroup
import android.view.ViewGroup.MarginLayoutParams
import android.widget.CompoundButton
import android.widget.TextView
import androidx.annotation.AnimatorRes
import androidx.annotation.AttrRes
import androidx.annotation.ColorInt
import androidx.annotation.ColorRes
import androidx.annotation.DimenRes
import androidx.annotation.DrawableRes
import androidx.annotation.Px
import androidx.constraintlayout.widget.ConstraintLayout
import androidx.core.content.res.ResourcesCompat
import androidx.core.view.marginBottom
import androidx.core.view.marginLeft
import androidx.core.view.marginRight
import androidx.core.view.marginTop
import com.yandex.divkit.demo.utils.coroutineScope
import com.yandex.divkit.demo.utils.noGetter
import kotlinx.coroutines.launch
const val matchParent: Int = ViewGroup.LayoutParams.MATCH_PARENT
const val wrapContent: Int = ViewGroup.LayoutParams.WRAP_CONTENT
const val matchConstraint: Int = ConstraintLayout.LayoutParams.MATCH_CONSTRAINT
var TextView.textColor: Int
@ColorInt get() = textColors.defaultColor
set(@ColorInt value) = setTextColor(value)
var TextView.textColorResource: Int
get() = noGetter()
set(colorId) = setTextColor(context.resources.run {
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) getColor(colorId, null) else getColor(colorId)
})
var TextView.textColorAttr: Int
get() = noGetter()
set(@AttrRes value) = setTextColor(context.getThemeColor(value))
var TextView.textColorHint: Int
get() = hintTextColors.defaultColor
set(value) = setHintTextColor(value)
var TextView.textColorHintResource: Int
get() = noGetter()
set(colorId) = setHintTextColor(context.resources.run {
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) getColor(colorId, null) else getColor(colorId)
})
var TextView.fontResource: Int
get() = noGetter()
set(fontId) {
typeface = ResourcesCompat.getFont(context, fontId)
}
var TextView.lineSpacingAdd: Float
get() = lineSpacingExtra
set(value) = setLineSpacing(value, lineSpacingMultiplier)
var TextView.lineSpacingMult: Float
get() = lineSpacingMultiplier
set(value) = setLineSpacing(lineSpacingExtra, value)
var TextView.textResource: Int
get() = noGetter()
set(v) = setText(v)
var TextView.hintResource: Int
get() = noGetter()
set(value) = setHint(value)
var View.backgroundColor: Int
get() = noGetter()
set(@ColorInt value) = setBackgroundColor(value)
var View.backgroundColorResource: Int
get() = noGetter()
set(@ColorRes value) = setBackgroundColor(ResourcesCompat.getColor(resources, value, null))
var View.backgroundColorAttr: Int
get() = noGetter()
set(@AttrRes value) = setBackgroundColor(context.getThemeColor(value))
var View.backgroundResource: Int
get() = noGetter()
set(@DrawableRes value) = setBackgroundResource(value)
var View.backgroundTintColor: Int
get() = noGetter()
set(@ColorInt value) {
background?.colorFilter = PorterDuffColorFilter(value, PorterDuff.Mode.SRC_ATOP)
}
var View.backgroundTintColorResource: Int
get() = noGetter()
set(@ColorRes value) {
backgroundTintColor = ResourcesCompat.getColor(resources, value, null)
}
fun View.onClick(value: suspend () -> Unit) {
setOnClickListener {
activity.coroutineScope.launch { value() }
}
}
fun View.clearClickListener() = setOnClickListener(null)
fun View.onLongClick(value: suspend () -> Unit) {
setOnLongClickListener {
activity.coroutineScope.launch { value() }
true
}
}
var View.leftPadding: Int
get() = paddingLeft
set(value) = setPadding(value, paddingTop, paddingRight, paddingBottom)
var View.rightPadding: Int
get() = paddingRight
set(value) = setPadding(paddingLeft, paddingTop, value, paddingBottom)
var View.topPadding: Int
get() = paddingTop
set(value) = setPadding(paddingLeft, value, paddingRight, paddingBottom)
var View.bottomPadding: Int
get() = paddingBottom
set(value) = setPadding(paddingLeft, paddingTop, paddingRight, value)
var View.padding: Int
get() = noGetter()
set(value) = setPadding(value, value, value, value)
var View.verticalPadding: Int
get() = noGetter()
set(value) = setPadding(paddingLeft, value, paddingRight, value)
var View.horizontalPadding: Int
get() = noGetter()
set(value) = setPadding(value, paddingTop, value, paddingBottom)
var View.stateListAnimatorResource: Int
get() = noGetter()
set(@AnimatorRes value) {
stateListAnimator = AnimatorInflater.loadStateListAnimator(context, value)
}
var View.topMargin: Int
get() = noGetter()
set(value) {
withMarginLayoutParams {
topMargin = value
}
}
var View.leftMargin: Int
get() = noGetter()
set(value) {
withMarginLayoutParams {
leftMargin = value
}
}
var View.bottomMargin: Int
get() = noGetter()
set(value) {
withMarginLayoutParams {
bottomMargin = value
}
}
var View.rightMargin: Int
get() = noGetter()
set(value) {
withMarginLayoutParams {
rightMargin = value
}
}
var View.topMarginRes: Int
get() = noGetter()
set(@DimenRes value) {
withMarginLayoutParams {
topMargin = context.resources.getDimensionPixelSize(value)
}
}
var View.leftMarginRes: Int
get() = noGetter()
set(@DimenRes value) {
withMarginLayoutParams {
leftMargin = context.resources.getDimensionPixelSize(value)
}
}
var View.bottomMarginRes: Int
get() = noGetter()
set(@DimenRes value) {
withMarginLayoutParams {
bottomMargin = context.resources.getDimensionPixelSize(value)
}
}
var View.rightMarginRes: Int
get() = noGetter()
set(@DimenRes value) {
withMarginLayoutParams {
rightMargin = context.resources.getDimensionPixelSize(value)
}
}
fun View.updateMargins(
@Px left: Int = marginLeft,
@Px top: Int = marginTop,
@Px right: Int = marginRight,
@Px bottom: Int = marginBottom
) {
if (marginLeft == left && marginTop == top && marginBottom == bottom && marginRight == right) {
return
}
withMarginLayoutParams {
topMargin = top
bottomMargin = bottom
leftMargin = left
rightMargin = right
}
}
inline fun View.withMarginLayoutParams(block: MarginLayoutParams.() -> Unit) = withLayoutParams(block)
inline fun <reified T : ViewGroup.LayoutParams> View.withLayoutParams(block: T.() -> Unit) {
(layoutParams as? T)?.let { lp ->
lp.block()
layoutParams = lp
}
}
inline fun View.onLayoutChange(crossinline onLayoutChange: () -> Unit) =
addOnLayoutChangeListener { _, _, _, _, _, _, _, _, _ -> onLayoutChange() }
inline fun CompoundButton.onCheckedChange(crossinline onCheckedChange: (Boolean) -> Unit) =
setOnCheckedChangeListener { _, isChecked -> onCheckedChange(isChecked) }
val View.activity: Activity
get() {
when (val context = context) {
is Activity -> return context
is ContextWrapper -> {
var contextWrapper = context
while (contextWrapper is ContextWrapper) {
if (contextWrapper is Activity) {
return contextWrapper
}
contextWrapper = contextWrapper.baseContext
}
error("Unknown view context $contextWrapper")
}
else -> error("Unknown view context $context")
}
}
@@ -2,7 +2,6 @@ package com.yandex.divkit.demo
import android.annotation.SuppressLint
import android.content.Context
import android.os.Build
import com.facebook.stetho.okhttp3.StethoInterceptor
import com.neovisionaries.ws.client.WebSocketFactory
import com.yandex.div.coil.CoilDivImageLoader
@@ -74,11 +73,9 @@ internal object Container {
.setSSLContext(NaiveSSLContext.getInstance("TLS"))
.setConnectionTimeout(TimeUnit.SECONDS.toMillis(15).toInt())
.apply {
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
context.connectivityManager.defaultProxy?.let { proxyInfo ->
proxySettings.host = proxyInfo.host
proxySettings.port = proxyInfo.port
}
context.connectivityManager.defaultProxy?.let { proxyInfo ->
proxySettings.host = proxyInfo.host
proxySettings.port = proxyInfo.port
}
}
}
@@ -9,7 +9,6 @@ import android.content.pm.PackageManager
import android.content.res.Configuration
import android.graphics.Path
import android.net.Uri
import android.os.Build
import android.os.Bundle
import android.view.View
import android.view.ViewGroup
@@ -26,7 +25,6 @@ import com.yandex.div.core.DivDataChangeListener
import com.yandex.div.core.DivViewFacade
import com.yandex.div.core.state.DivStateChangeListener
import com.yandex.div.core.state.DivStateTransition
import com.yandex.div.core.util.SafeAlertDialogBuilder
import com.yandex.div.core.view2.Div2View
import com.yandex.div.data.Variable
import com.yandex.div.font.YandexSansDisplayDivTypefaceProvider
@@ -280,13 +278,7 @@ class Div2Activity : AppCompatActivity() {
private fun (() -> Unit).invokeWithPermissions(vararg permissions: String) =
when {
checkPermissions(permissions) -> invoke()
Build.VERSION.SDK_INT >= Build.VERSION_CODES.M ->
requestPermissions(permissions, OPEN_FILE_ACTIVITY_REQUEST_CODE)
else -> SafeAlertDialogBuilder(this@Div2Activity)
.setTitle("Unable to add new template from storage: no permissions")
.setPositiveButton("Ok") { dialog, id ->
dialog.cancel()
}
else -> requestPermissions(permissions, OPEN_FILE_ACTIVITY_REQUEST_CODE)
}
private fun checkPermissions(permissions: Array<out String>) =
@@ -5,7 +5,6 @@ import android.content.Intent
import android.content.pm.PackageManager
import android.content.res.Configuration
import android.graphics.Color
import android.os.Build
import android.os.Bundle
import android.text.TextUtils
import android.view.View
@@ -175,12 +174,8 @@ open class DivActivity : AppCompatActivity() {
private fun (()->Unit).invokeWithPermissions(vararg permissions: String) =
when {
checkPermissions(permissions) ->
invoke()
Build.VERSION.SDK_INT >= Build.VERSION_CODES.M ->
requestPermissions(permissions, REQUEST_CODE)
else ->
noPermissions()
checkPermissions(permissions) -> invoke()
else -> requestPermissions(permissions, REQUEST_CODE)
}
private fun checkPermissions(permissions: Array<out String>) =
@@ -1,7 +1,6 @@
package com.yandex.divkit.demo.screenshot
import android.content.Context
import android.os.Build
import com.yandex.div.internal.util.IOUtils
import org.json.JSONObject
@@ -9,21 +8,6 @@ class DivAssetReader(private val context: Context) {
fun read(filename: String): JSONObject {
val data = IOUtils.toString(context.assets.open(filename))
.replaceUnsupportedUnicodeSymbols()
return JSONObject(data)
}
private fun String.replaceUnsupportedUnicodeSymbols(): String {
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) return this
var result = this
UNSUPPORTED_UNICODE_PRE_M.forEachIndexed { index, unsupportedString ->
result = result.replace(unsupportedString, UNSUPPORTED_UNICODE_REPLACEMENTS[index])
}
return result
}
companion object {
private val UNSUPPORTED_UNICODE_PRE_M = arrayOf("⌜", "⌝", "⌞", "⌄", "⌟")
private val UNSUPPORTED_UNICODE_REPLACEMENTS = arrayOf("tl", "tr", "bl", "b", "br")
}
}
@@ -4,7 +4,6 @@ import android.content.ActivityNotFoundException
import android.content.ComponentName
import android.content.Context
import android.net.Uri
import android.os.Build
import android.os.Parcelable
import androidx.annotation.ColorInt
import androidx.browser.customtabs.CustomTabsClient
@@ -32,11 +31,7 @@ class CustomTabHelper(private val context: Context) {
}
val intentBuilder = CustomTabsIntent.Builder(customTabSession)
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
intentBuilder.setToolbarColor(context.resources.getColor(R.color.colorPrimary, null))
} else {
intentBuilder.setToolbarColor(context.resources.getColor(R.color.colorPrimary))
}
intentBuilder.setToolbarColor(context.resources.getColor(R.color.colorPrimary, null))
if (colors != null) {
colors.toolbarColor?.let { intentBuilder.setToolbarColor(it) }
@@ -3,7 +3,6 @@ package com.yandex.divkit.demo.utils
import android.Manifest
import android.app.Activity
import android.content.pm.PackageManager
import android.os.Build
import android.widget.Toast
import androidx.annotation.StringRes
import androidx.core.app.ActivityCompat
@@ -12,49 +11,24 @@ import androidx.core.content.ContextCompat
/**
* Encapsulates permissions requesting logic
*/
class DivkitDemoPermissionHelper(private val activity: Activity) {
object DivkitDemoPermissionHelper {
companion object {
const val LOCATION_PERMISSION_REQUEST_CODE = 101
private const val READ_EXTERNAL_STORAGE_PERMISSION_REQUEST_CODE = 102
private const val READ_EXTERNAL_STORAGE_PERMISSION_REQUEST_CODE = 102
private const val READ_EXTERNAL_STORAGE = Manifest.permission.READ_EXTERNAL_STORAGE
const val ACCESS_COARSE_LOCATION = Manifest.permission.ACCESS_COARSE_LOCATION
const val ACCESS_FINE_LOCATION = Manifest.permission.ACCESS_FINE_LOCATION
private const val READ_EXTERNAL_STORAGE = Manifest.permission.READ_EXTERNAL_STORAGE
fun requestReadExternalStoragePermission(activity: Activity, @StringRes rationale: Int) {
if (ContextCompat.checkSelfPermission(activity, READ_EXTERNAL_STORAGE) == PackageManager.PERMISSION_GRANTED) {
return
}
fun requestReadExternalStoragePermission(activity: Activity, @StringRes rationale: Int) {
if (ContextCompat.checkSelfPermission(activity, READ_EXTERNAL_STORAGE) == PackageManager.PERMISSION_GRANTED) {
return
}
if (ActivityCompat.shouldShowRequestPermissionRationale(activity, READ_EXTERNAL_STORAGE)) {
Toast.makeText(activity, rationale, Toast.LENGTH_LONG).show()
} else {
ActivityCompat.requestPermissions(activity, arrayOf(READ_EXTERNAL_STORAGE), READ_EXTERNAL_STORAGE_PERMISSION_REQUEST_CODE)
}
if (ActivityCompat.shouldShowRequestPermissionRationale(activity, READ_EXTERNAL_STORAGE)) {
Toast.makeText(activity, rationale, Toast.LENGTH_LONG).show()
} else {
ActivityCompat.requestPermissions(
activity,
arrayOf(READ_EXTERNAL_STORAGE),
READ_EXTERNAL_STORAGE_PERMISSION_REQUEST_CODE
)
}
}
private var locationPermissionGrantedCallback: Runnable? = null
fun requestLocationPermission(callback: Runnable) {
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
activity.requestPermissions(arrayOf(ACCESS_COARSE_LOCATION, ACCESS_FINE_LOCATION), LOCATION_PERMISSION_REQUEST_CODE)
locationPermissionGrantedCallback = callback
}
}
fun onRequestPermissionsResult(requestCode: Int, permissions: Array<out String>, grantResults: IntArray) {
when (requestCode) {
LOCATION_PERMISSION_REQUEST_CODE -> {
if (isGranted(ACCESS_COARSE_LOCATION, permissions, grantResults) || isGranted(ACCESS_FINE_LOCATION, permissions, grantResults)) {
locationPermissionGrantedCallback?.run()
locationPermissionGrantedCallback = null
}
}
}
}
private fun isGranted(permission: String, permissions: Array<out String>, grantResults: IntArray) =
permissions.contains(permission) && grantResults[permissions.indexOf(permission)] == PackageManager.PERMISSION_GRANTED
}
@@ -1,33 +0,0 @@
package com.yandex.divkit.demo.utils
import android.content.Context
import androidx.annotation.WorkerThread
import org.json.JSONException
import org.json.JSONObject
import java.io.IOException
internal class JsonAssetReader(
private val context: Context
) {
@WorkerThread
@Throws(IOException::class)
fun readBytes(filename: String): ByteArray {
val stream = context.assets.open(filename)
stream.use {
return stream.readBytes()
}
}
@WorkerThread
@Throws(IOException::class)
fun readText(filename: String): String {
return readBytes(filename).toString(charset = Charsets.UTF_8)
}
@WorkerThread
@Throws(IOException::class, JSONException::class)
fun readJson(filename: String): JSONObject {
return JSONObject(readText(filename))
}
}
Binary file not shown.

Before

Width:  |  Height:  |  Size: 13 KiB

After

Width:  |  Height:  |  Size: 13 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 2.0 KiB

After

Width:  |  Height:  |  Size: 2.0 KiB

+1 -1
View File
@@ -41,7 +41,7 @@ androidx-annotation = "1.9.1"
androidx-appcompat = "1.6.1"
androidx-browser = "1.8.0"
androidx-collection = "1.5.0"
androidx-compose-bom = "2024.04.00"
androidx-compose-bom = "2026.03.00"
androidx-constraint = "2.2.1"
androidx-core = "1.16.0"
androidx-espresso = "3.7.0"
@@ -0,0 +1,13 @@
package com.yandex.div.test.data
import com.yandex.div.evaluable.types.Color
import com.yandex.div2.DivBackground
import com.yandex.div2.DivSolidBackground
fun solidBackground(color: Color): DivBackground {
return DivBackground.Solid(
value = DivSolidBackground(
color = constant(color.value)
)
)
}
@@ -1,16 +1,24 @@
package com.yandex.div.test.data
import com.yandex.div2.Div
import com.yandex.div2.DivBackground
import com.yandex.div2.DivContainer
import com.yandex.div2.DivEdgeInsets
import com.yandex.div2.DivVariable
fun container(
backgrounds: List<DivBackground>? = null,
items: List<Div> = emptyList(),
margins: DivEdgeInsets? = null,
paddings: DivEdgeInsets? = null,
variables: List<DivVariable>? = null
): Div {
return Div.Container(
value = DivContainer(
background = backgrounds,
items = items,
margins = margins,
paddings = paddings,
variables = variables
)
)
@@ -1,5 +1,6 @@
package com.yandex.div.test.data
import com.yandex.div.evaluable.types.Color
import com.yandex.div.json.expressions.Expression
import com.yandex.div2.Div
import com.yandex.div2.DivAction
@@ -10,10 +11,30 @@ import com.yandex.div2.DivVariable
import com.yandex.div2.DivVisibilityAction
fun text(
action: DivAction? = null,
disappearActions: List<DivDisappearAction>? = null,
id: String? = null,
text: String,
triggers: List<DivTrigger>? = null,
variables: List<DivVariable>? = null,
visibilityActions: List<DivVisibilityAction>? = null
): Div {
return text(
disappearActions = disappearActions,
id = id,
text = constant(text),
triggers = triggers,
variables = variables,
visibilityActions = visibilityActions
)
}
fun text(
action: DivAction? = null,
disappearActions: List<DivDisappearAction>? = null,
fontSize: Long = 12,
id: String? = null,
text: Expression<String>,
textColor: Color? = null,
triggers: List<DivTrigger>? = null,
variables: List<DivVariable>? = null,
visibilityActions: List<DivVisibilityAction>? = null
@@ -22,8 +43,10 @@ fun text(
value = DivText(
action = action,
disappearActions = disappearActions,
fontSize = constant(fontSize),
id = id,
text = text,
textColor = textColor?.let { constant(it.value) } ?: constant(0xFF000000.toInt()),
variables = variables,
variableTriggers = triggers,
visibilityActions = visibilityActions