diff --git a/.mapping.json b/.mapping.json index adc94331c..525daa4b3 100644 --- a/.mapping.json +++ b/.mapping.json @@ -600,8 +600,14 @@ "client/android/compose/proguard-rules.pro":"divkit/public/client/android/compose/proguard-rules.pro", "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/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/dagger/DivContextComponent.kt":"divkit/public/client/android/compose/src/main/kotlin/com/yandex/div/compose/dagger/DivContextComponent.kt", + "client/android/compose/src/main/kotlin/com/yandex/div/compose/dagger/DivContextModule.kt":"divkit/public/client/android/compose/src/main/kotlin/com/yandex/div/compose/dagger/DivContextModule.kt", + "client/android/compose/src/main/kotlin/com/yandex/div/compose/dagger/DivContextScope.kt":"divkit/public/client/android/compose/src/main/kotlin/com/yandex/div/compose/dagger/DivContextScope.kt", + "client/android/compose/src/main/kotlin/com/yandex/div/compose/expressions/DivComposeExpressionResolver.kt":"divkit/public/client/android/compose/src/main/kotlin/com/yandex/div/compose/expressions/DivComposeExpressionResolver.kt", + "client/android/compose/src/main/kotlin/com/yandex/div/compose/expressions/EvaluatorWarningSender.kt":"divkit/public/client/android/compose/src/main/kotlin/com/yandex/div/compose/expressions/EvaluatorWarningSender.kt", "client/android/compose/src/main/kotlin/com/yandex/div/compose/views/DivBlockView.kt":"divkit/public/client/android/compose/src/main/kotlin/com/yandex/div/compose/views/DivBlockView.kt", "client/android/compose/src/main/kotlin/com/yandex/div/compose/views/DivContainerView.kt":"divkit/public/client/android/compose/src/main/kotlin/com/yandex/div/compose/views/DivContainerView.kt", "client/android/compose/src/main/kotlin/com/yandex/div/compose/views/DivImageView.kt":"divkit/public/client/android/compose/src/main/kotlin/com/yandex/div/compose/views/DivImageView.kt", @@ -610,6 +616,7 @@ "client/android/compose/src/main/kotlin/com/yandex/div/compose/views/modifiers/BackgroundModifiers.kt":"divkit/public/client/android/compose/src/main/kotlin/com/yandex/div/compose/views/modifiers/BackgroundModifiers.kt", "client/android/compose/src/main/kotlin/com/yandex/div/compose/views/modifiers/Modifiers.kt":"divkit/public/client/android/compose/src/main/kotlin/com/yandex/div/compose/views/modifiers/Modifiers.kt", "client/android/compose/src/main/kotlin/com/yandex/div/compose/views/modifiers/SizeModifiers.kt":"divkit/public/client/android/compose/src/main/kotlin/com/yandex/div/compose/views/modifiers/SizeModifiers.kt", + "client/android/compose/src/test/kotlin/com/yandex/div/compose/expressions/DivComposeExpressionResolverTest.kt":"divkit/public/client/android/compose/src/test/kotlin/com/yandex/div/compose/expressions/DivComposeExpressionResolverTest.kt", "client/android/div-common.gradle":"divkit/public/client/android/div-common.gradle", "client/android/div-core/build.gradle":"divkit/public/client/android/div-core/build.gradle", "client/android/div-core/proguard-rules.pro":"divkit/public/client/android/div-core/proguard-rules.pro", diff --git a/client/android/compose/build.gradle.kts b/client/android/compose/build.gradle.kts index dac20abd7..ec006bbff 100644 --- a/client/android/compose/build.gradle.kts +++ b/client/android/compose/build.gradle.kts @@ -1,16 +1,21 @@ plugins { alias(libs.plugins.android.library) alias(libs.plugins.kotlin.compose) + alias(libs.plugins.kotlin.ksp) } apply(from = "../div-library.gradle") +apply(from = "../div-tests.gradle") android { namespace = "com.yandex.div.compose" } dependencies { + implementation(project(":div-core")) implementation(project(":div-data")) + implementation(project(":div-evaluable")) + implementation(project(":logging")) implementation(platform(libs.androidx.compose.bom)) implementation(libs.androidx.compose.material3) @@ -18,6 +23,9 @@ dependencies { implementation(libs.androidx.coreKtx) implementation(libs.coil.compose) implementation(libs.coil.network) + implementation(libs.yatagan.api.compiled) + + ksp(libs.yatagan.processor.ksp) debugImplementation(libs.androidx.compose.ui.tooling) } diff --git a/client/android/compose/src/main/kotlin/com/yandex/div/compose/DivComposeConfiguration.kt b/client/android/compose/src/main/kotlin/com/yandex/div/compose/DivComposeConfiguration.kt index d872bd85c..8fd9244c7 100644 --- a/client/android/compose/src/main/kotlin/com/yandex/div/compose/DivComposeConfiguration.kt +++ b/client/android/compose/src/main/kotlin/com/yandex/div/compose/DivComposeConfiguration.kt @@ -1,13 +1,21 @@ package com.yandex.div.compose import android.content.Context -import com.yandex.div.json.expressions.ExpressionResolver +import com.yandex.div.compose.dagger.`Yatagan$DivContextComponent` +import com.yandex.div.core.annotations.PublicApi +import com.yandex.yatagan.Module +import com.yandex.yatagan.Provides -class DivComposeConfiguration { - fun createContext(baseContext: Context): DivContext { - return DivContext( - baseContext = baseContext, - expressionResolver = ExpressionResolver.EMPTY - ) - } +@Module +@PublicApi +class DivComposeConfiguration( + @get:Provides val reporter: DivReporter = DivReporter() +) + +fun DivComposeConfiguration.createContext(baseContext: Context): DivContext { + return `Yatagan$DivContextComponent`.builder() + .baseContext(baseContext) + .configuration(this) + .build() + .context } diff --git a/client/android/compose/src/main/kotlin/com/yandex/div/compose/DivContext.kt b/client/android/compose/src/main/kotlin/com/yandex/div/compose/DivContext.kt index bdf3c4d2d..ce3de3bcb 100644 --- a/client/android/compose/src/main/kotlin/com/yandex/div/compose/DivContext.kt +++ b/client/android/compose/src/main/kotlin/com/yandex/div/compose/DivContext.kt @@ -3,9 +3,13 @@ package com.yandex.div.compose import android.content.Context import android.content.ContextWrapper import androidx.annotation.MainThread +import com.yandex.div.core.annotations.PublicApi import com.yandex.div.json.expressions.ExpressionResolver +import javax.inject.Inject -class DivContext @MainThread internal constructor( +@PublicApi +class DivContext @Inject @MainThread internal constructor( baseContext: Context, - internal val expressionResolver: ExpressionResolver + internal val expressionResolver: ExpressionResolver, + internal val reporter: DivReporter ) : ContextWrapper(baseContext) diff --git a/client/android/compose/src/main/kotlin/com/yandex/div/compose/DivReporter.kt b/client/android/compose/src/main/kotlin/com/yandex/div/compose/DivReporter.kt new file mode 100644 index 000000000..8d8121677 --- /dev/null +++ b/client/android/compose/src/main/kotlin/com/yandex/div/compose/DivReporter.kt @@ -0,0 +1,22 @@ +package com.yandex.div.compose + +import com.yandex.div.core.annotations.PublicApi +import com.yandex.div.internal.Log + +@PublicApi +open class DivReporter { + + open fun reportError(message: String) { + Log.e(TAG, message) + } + + open fun reportError(message: String, e: Throwable) { + Log.e(TAG, message, e) + } + + open fun reportWarning(message: String) { + Log.w(TAG, message) + } +} + +private const val TAG = "DivReporter" diff --git a/client/android/compose/src/main/kotlin/com/yandex/div/compose/dagger/DivContextComponent.kt b/client/android/compose/src/main/kotlin/com/yandex/div/compose/dagger/DivContextComponent.kt new file mode 100644 index 000000000..257c559d8 --- /dev/null +++ b/client/android/compose/src/main/kotlin/com/yandex/div/compose/dagger/DivContextComponent.kt @@ -0,0 +1,31 @@ +package com.yandex.div.compose.dagger + +import android.content.Context +import com.yandex.div.compose.DivComposeConfiguration +import com.yandex.div.compose.DivContext +import com.yandex.yatagan.BindsInstance +import com.yandex.yatagan.Component + +@DivContextScope +@Component( + modules = [ + DivComposeConfiguration::class, + DivContextModule::class + ] +) +internal interface DivContextComponent { + + val context: DivContext + + @Component.Builder + interface Builder { + + @BindsInstance + fun baseContext(baseContext: Context): Builder + + @BindsInstance + fun configuration(configuration: DivComposeConfiguration): Builder + + fun build(): DivContextComponent + } +} diff --git a/client/android/compose/src/main/kotlin/com/yandex/div/compose/dagger/DivContextModule.kt b/client/android/compose/src/main/kotlin/com/yandex/div/compose/dagger/DivContextModule.kt new file mode 100644 index 000000000..0210a73a6 --- /dev/null +++ b/client/android/compose/src/main/kotlin/com/yandex/div/compose/dagger/DivContextModule.kt @@ -0,0 +1,35 @@ +package com.yandex.div.compose.dagger + +import com.yandex.div.compose.expressions.DivComposeExpressionResolver +import com.yandex.div.compose.expressions.EvaluatorWarningSender +import com.yandex.div.evaluable.EvaluationContext +import com.yandex.div.evaluable.Evaluator +import com.yandex.div.evaluable.function.GeneratedBuiltinFunctionProvider +import com.yandex.div.json.expressions.ExpressionResolver +import com.yandex.yatagan.Binds +import com.yandex.yatagan.Module +import com.yandex.yatagan.Provides + +@Module +internal interface DivContextModule { + + @Binds + fun bindExpressionResolver(impl: DivComposeExpressionResolver): ExpressionResolver + + companion object { + + @DivContextScope + @Provides + fun providerEvaluator( + warningSender: EvaluatorWarningSender + ): Evaluator { + val evaluationContext = EvaluationContext( + variableProvider = { _ -> null }, + storedValueProvider = { _ -> null }, + functionProvider = GeneratedBuiltinFunctionProvider, + warningSender = warningSender + ) + return Evaluator(evaluationContext) + } + } +} diff --git a/client/android/compose/src/main/kotlin/com/yandex/div/compose/dagger/DivContextScope.kt b/client/android/compose/src/main/kotlin/com/yandex/div/compose/dagger/DivContextScope.kt new file mode 100644 index 000000000..41c5014de --- /dev/null +++ b/client/android/compose/src/main/kotlin/com/yandex/div/compose/dagger/DivContextScope.kt @@ -0,0 +1,7 @@ +package com.yandex.div.compose.dagger + +import javax.inject.Scope + +@Scope +@Retention(AnnotationRetention.RUNTIME) +internal annotation class DivContextScope diff --git a/client/android/compose/src/main/kotlin/com/yandex/div/compose/expressions/DivComposeExpressionResolver.kt b/client/android/compose/src/main/kotlin/com/yandex/div/compose/expressions/DivComposeExpressionResolver.kt new file mode 100644 index 000000000..24d2eb886 --- /dev/null +++ b/client/android/compose/src/main/kotlin/com/yandex/div/compose/expressions/DivComposeExpressionResolver.kt @@ -0,0 +1,187 @@ +package com.yandex.div.compose.expressions + +import com.yandex.div.compose.DivReporter +import com.yandex.div.compose.dagger.DivContextScope +import com.yandex.div.core.Disposable +import com.yandex.div.core.ObserverList +import com.yandex.div.evaluable.Evaluable +import com.yandex.div.evaluable.EvaluableException +import com.yandex.div.evaluable.Evaluator +import com.yandex.div.evaluable.MissingVariableException +import com.yandex.div.internal.parser.Converter +import com.yandex.div.internal.parser.TypeHelper +import com.yandex.div.internal.parser.ValueValidator +import com.yandex.div.json.ParsingErrorLogger +import com.yandex.div.json.expressions.ExpressionResolver +import com.yandex.div.json.invalidValue +import com.yandex.div.json.missingVariable +import com.yandex.div.json.resolveFailed +import com.yandex.div.json.typeMismatch +import javax.inject.Inject + +@DivContextScope +internal class DivComposeExpressionResolver @Inject constructor( + private val evaluator: Evaluator, + private val reporter: DivReporter +) : ExpressionResolver { + + private val evaluationsCache = mutableMapOf() + private val varToExpressions = mutableMapOf>() + private val expressionObservers = mutableMapOf Unit>>() + + override fun get( + expressionKey: String, + rawExpression: String, + evaluable: Evaluable, + converter: Converter?, + validator: ValueValidator, + fieldType: TypeHelper, + logger: ParsingErrorLogger + ): T { + return try { + tryResolve(expressionKey, rawExpression, evaluable, converter, validator, fieldType) + } catch (e: Exception) { + reporter.reportError("Failed to resolve expression", e) + throw e + } + } + + override fun subscribeToExpression( + rawExpression: String, + variableNames: List, + callback: () -> Unit + ): Disposable { + variableNames.forEach { + val expressions = varToExpressions.getOrPut(it) { mutableSetOf() } + expressions.add(rawExpression) + } + val observers = expressionObservers.getOrPut(rawExpression) { ObserverList() } + observers.addObserver(callback) + return Disposable { expressionObservers[rawExpression]?.removeObserver(callback) } + } + + private fun tryResolve( + expressionKey: String, + rawExpression: String, + evaluable: Evaluable, + converter: Converter?, + validator: ValueValidator, + fieldType: TypeHelper, + ): T { + val result = try { + getEvaluationResult(rawExpression, evaluable) + } catch (e: EvaluableException) { + val variableName = tryGetMissingVariableName(e) + if (variableName != null) { + throw missingVariable(rawExpression, variableName, e) + } + throw resolveFailed(expressionKey, rawExpression, e) + } + + val convertedValue = if (fieldType.isTypeValid(result)) { + result as T + } else { + safeConvert(expressionKey, rawExpression, converter, result, fieldType) + ?: throw invalidValue(expressionKey, rawExpression, result) + } + + safeValidate(expressionKey, rawExpression, validator, convertedValue) + + return convertedValue + } + + private fun getEvaluationResult( + rawExpression: String, + evaluable: Evaluable + ): R { + val cachedValue = evaluationsCache[rawExpression] + if (cachedValue != null) { + return cachedValue as R + } + + val value = evaluator.eval(evaluable) + if (evaluable.checkIsCacheable()) { + evaluable.variables.forEach { + val expressions = varToExpressions.getOrPut(it) { mutableSetOf() } + expressions.add(rawExpression) + } + evaluationsCache[rawExpression] = value + } + return value + } + + private fun tryGetMissingVariableName(e: EvaluableException): String? { + return if (e is MissingVariableException) e.variableName else null + } + + private fun safeConvert( + expressionKey: String, + rawExpression: String, + converter: Converter?, + rawValue: R, + fieldType: TypeHelper + ): T? { + val convertedValue = if (converter == null) { + // cast is unsafe but we hope that safeValidate() will check this type later + rawValue as? T + } else { + try { + // We need to guard with try..catch because at this point we're + // not sure that resolved expression type is same type that converter accepts. + converter.invoke(rawValue) + } catch (e: ClassCastException) { + throw typeMismatch( + expressionKey = expressionKey, + rawExpression = rawExpression, + wrongTypeValue = rawValue, + cause = e, + ) + } catch (e: Exception) { + // okay to catch exceptions here cause convertors may + // throw different types of errors from common ClassCastException + // up to specific NumberFormatException. + throw invalidValue( + expressionKey = expressionKey, + rawExpression = rawExpression, + wrongValue = rawValue, + cause = e, + ) + } + } + + fun fieldAwaitsStringButValueNotConverted(value: T?): Boolean { + if (value == null) { + return false + } + return fieldType.typeDefault is String && !fieldType.isTypeValid(value) + } + + return if (fieldAwaitsStringButValueNotConverted(convertedValue)) { + convertedValue.toString() as T + } else { + convertedValue + } + } + + private fun safeValidate( + expressionKey: String, + rawExpression: String, + validator: ValueValidator, + convertedValue: T + ) { + try { + // We need to guard with try..catch because at this point we're + // not sure that resolved expression type is same type that validator checks. + if (!validator.isValid(convertedValue)) { + throw invalidValue(rawExpression, convertedValue) + } + } catch (e: ClassCastException) { + throw typeMismatch( + expressionKey = expressionKey, + rawExpression = rawExpression, + wrongTypeValue = convertedValue, + cause = e, + ) + } + } +} diff --git a/client/android/compose/src/main/kotlin/com/yandex/div/compose/expressions/EvaluatorWarningSender.kt b/client/android/compose/src/main/kotlin/com/yandex/div/compose/expressions/EvaluatorWarningSender.kt new file mode 100644 index 000000000..4684e580e --- /dev/null +++ b/client/android/compose/src/main/kotlin/com/yandex/div/compose/expressions/EvaluatorWarningSender.kt @@ -0,0 +1,20 @@ +package com.yandex.div.compose.expressions + +import com.yandex.div.compose.DivReporter +import com.yandex.div.compose.dagger.DivContextScope +import com.yandex.div.evaluable.ExpressionContext +import com.yandex.div.evaluable.WarningSender +import javax.inject.Inject + +@DivContextScope +internal class EvaluatorWarningSender @Inject constructor( + private val reporter: DivReporter +): WarningSender { + + override fun send( + expressionContext: ExpressionContext, + message: String + ) { + reporter.reportWarning(message) + } +} diff --git a/client/android/compose/src/main/kotlin/com/yandex/div/compose/views/DivBlockView.kt b/client/android/compose/src/main/kotlin/com/yandex/div/compose/views/DivBlockView.kt index 013bf8685..c01efd9b5 100644 --- a/client/android/compose/src/main/kotlin/com/yandex/div/compose/views/DivBlockView.kt +++ b/client/android/compose/src/main/kotlin/com/yandex/div/compose/views/DivBlockView.kt @@ -15,6 +15,6 @@ internal fun DivBlockView( is Div.Container -> DivContainerView(modifier, data.value) is Div.Image -> DivImageView(modifier, data.value) is Div.Text -> DivTextView(modifier, data.value) - else -> TODO() + else -> reporter.reportError("Element not supported") } } diff --git a/client/android/compose/src/main/kotlin/com/yandex/div/compose/views/Utils.kt b/client/android/compose/src/main/kotlin/com/yandex/div/compose/views/Utils.kt index 809fd9a10..35b64c986 100644 --- a/client/android/compose/src/main/kotlin/com/yandex/div/compose/views/Utils.kt +++ b/client/android/compose/src/main/kotlin/com/yandex/div/compose/views/Utils.kt @@ -6,6 +6,7 @@ import androidx.compose.ui.platform.LocalContext import androidx.compose.ui.unit.Dp import androidx.compose.ui.unit.dp import com.yandex.div.compose.DivContext +import com.yandex.div.compose.DivReporter import com.yandex.div.json.expressions.Expression import com.yandex.div.json.expressions.ExpressionResolver @@ -25,6 +26,10 @@ internal val expressionResolver: ExpressionResolver @Composable get() = divContext.expressionResolver +internal val reporter: DivReporter + @Composable + get() = divContext.reporter + @Composable internal fun Expression.evaluate(): T { return evaluate(expressionResolver) diff --git a/client/android/compose/src/main/kotlin/com/yandex/div/compose/views/modifiers/BackgroundModifiers.kt b/client/android/compose/src/main/kotlin/com/yandex/div/compose/views/modifiers/BackgroundModifiers.kt index 5edfc281c..4563b8941 100644 --- a/client/android/compose/src/main/kotlin/com/yandex/div/compose/views/modifiers/BackgroundModifiers.kt +++ b/client/android/compose/src/main/kotlin/com/yandex/div/compose/views/modifiers/BackgroundModifiers.kt @@ -4,8 +4,13 @@ import androidx.compose.foundation.background import androidx.compose.runtime.Composable import androidx.compose.ui.Modifier import com.yandex.div.compose.views.evaluate +import com.yandex.div.compose.views.reporter import com.yandex.div.compose.views.toColor import com.yandex.div2.DivBackground +import com.yandex.div2.DivImageBackground +import com.yandex.div2.DivLinearGradient +import com.yandex.div2.DivNinePatchBackground +import com.yandex.div2.DivRadialGradient import com.yandex.div2.DivSolidBackground @Composable @@ -16,10 +21,17 @@ internal fun Modifier.backgrounds(value: List): Modifier { is DivBackground.Solid -> modifier = modifier.solidBackground(background.value) - is DivBackground.Image -> TODO() - is DivBackground.LinearGradient -> TODO() - is DivBackground.NinePatch -> TODO() - is DivBackground.RadialGradient -> TODO() + is DivBackground.Image -> + reporter.reportError("Background not supported: ${DivImageBackground.TYPE}") + + is DivBackground.LinearGradient -> + reporter.reportError("Background not supported: ${DivLinearGradient.TYPE}") + + is DivBackground.NinePatch -> + reporter.reportError("Background not supported: ${DivNinePatchBackground.TYPE}") + + is DivBackground.RadialGradient -> + reporter.reportError("Background not supported: ${DivRadialGradient.TYPE}") } } return modifier diff --git a/client/android/compose/src/test/kotlin/com/yandex/div/compose/expressions/DivComposeExpressionResolverTest.kt b/client/android/compose/src/test/kotlin/com/yandex/div/compose/expressions/DivComposeExpressionResolverTest.kt new file mode 100644 index 000000000..b95116278 --- /dev/null +++ b/client/android/compose/src/test/kotlin/com/yandex/div/compose/expressions/DivComposeExpressionResolverTest.kt @@ -0,0 +1,100 @@ +package com.yandex.div.compose.expressions + +import com.yandex.div.compose.DivReporter +import com.yandex.div.data.Variable +import com.yandex.div.evaluable.EvaluationContext +import com.yandex.div.evaluable.Evaluator +import com.yandex.div.evaluable.function.GeneratedBuiltinFunctionProvider +import com.yandex.div.internal.parser.TYPE_HELPER_STRING +import com.yandex.div.internal.parser.TypeHelper +import com.yandex.div.json.expressions.Expression +import org.junit.Assert.assertEquals +import org.junit.Assert.fail +import org.junit.Test +import org.junit.runner.RunWith +import org.mockito.kotlin.mock +import org.robolectric.RobolectricTestRunner + +@RunWith(RobolectricTestRunner::class) +class DivComposeExpressionResolverTest { + + private val variables = mutableMapOf() + + private val evaluationContext = EvaluationContext( + variableProvider = { variables[it]?.getValue() }, + storedValueProvider = mock(), + functionProvider = GeneratedBuiltinFunctionProvider, + warningSender = { _, _ -> } + ) + + private val reporter = object : DivReporter() { + override fun reportError(message: String) { + fail(message) + } + + override fun reportError(message: String, e: Throwable) { + fail(message) + } + } + + private val expressionResolver = DivComposeExpressionResolver( + Evaluator(evaluationContext), + reporter + ) + + @Test + fun `expression with string function`() { + assertEquals( + "length = 9", + evaluate("length = @{len('123456789')}") + ) + } + + @Test + fun `expression with string variables`() { + variables["host"] = Variable.StringVariable("host", "test.abc") + variables["path"] = Variable.StringVariable("path", "path") + + assertEquals( + "https://test.abc/path", + evaluate("https://@{host}/@{path}") + ) + } + + @Test + fun `expression with integer variable`() { + variables["value"] = Variable.IntegerVariable("value", 100) + + assertEquals( + "value + 10 = 110", + evaluate("value + 10 = @{value + 10}") + ) + } + + @Test + fun `expression with double variable`() { + variables["value"] = Variable.DoubleVariable("value", 123.45) + + assertEquals( + "value + 10 = 133.45", + evaluate("value + 10 = @{value + 10}") + ) + } + + private fun evaluate(expression: String): String { + return expression(expression, TYPE_HELPER_STRING).evaluate(expressionResolver) + } + + private fun expression( + rawExpression: String, + typeHelper: TypeHelper, + validator: (T) -> Boolean = { true }, + ) = Expression.MutableExpression( + expressionKey = "test", + rawExpression = rawExpression, + validator = validator, + converter = { it }, + logger = { fail(it.message) }, + typeHelper = typeHelper, + ) +} diff --git a/client/android/divkit-demo-app/src/main/java/com/yandex/divkit/demo/screenshot/DivComposeScreenshotActivity.kt b/client/android/divkit-demo-app/src/main/java/com/yandex/divkit/demo/screenshot/DivComposeScreenshotActivity.kt index c11149427..f816ff1c9 100644 --- a/client/android/divkit-demo-app/src/main/java/com/yandex/divkit/demo/screenshot/DivComposeScreenshotActivity.kt +++ b/client/android/divkit-demo-app/src/main/java/com/yandex/divkit/demo/screenshot/DivComposeScreenshotActivity.kt @@ -6,6 +6,7 @@ import androidx.activity.ComponentActivity import androidx.compose.ui.platform.ComposeView import com.yandex.div.compose.DivComposeConfiguration import com.yandex.div.compose.DivView +import com.yandex.div.compose.createContext import com.yandex.div.data.DivParsingEnvironment import com.yandex.div.json.ParsingErrorLogger import com.yandex.div2.DivData