mirror of
https://github.com/divkit/divkit.git
synced 2026-06-06 20:07:59 +00:00
Added DivLocalContext storage
commit_hash:08dcc6c443daa3bb8d952b0637bf492706801c2c
This commit is contained in:
+5
-1
@@ -613,6 +613,10 @@
|
||||
"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",
|
||||
"client/android/compose/src/main/kotlin/com/yandex/div/compose/actions/DivCustomActionHandler.kt":"divkit/public/client/android/compose/src/main/kotlin/com/yandex/div/compose/actions/DivCustomActionHandler.kt",
|
||||
"client/android/compose/src/main/kotlin/com/yandex/div/compose/actions/SetVariableActionHandler.kt":"divkit/public/client/android/compose/src/main/kotlin/com/yandex/div/compose/actions/SetVariableActionHandler.kt",
|
||||
"client/android/compose/src/main/kotlin/com/yandex/div/compose/context/DivLocalContext.kt":"divkit/public/client/android/compose/src/main/kotlin/com/yandex/div/compose/context/DivLocalContext.kt",
|
||||
"client/android/compose/src/main/kotlin/com/yandex/div/compose/context/DivLocalContextStorage.kt":"divkit/public/client/android/compose/src/main/kotlin/com/yandex/div/compose/context/DivLocalContextStorage.kt",
|
||||
"client/android/compose/src/main/kotlin/com/yandex/div/compose/context/DivViewContext.kt":"divkit/public/client/android/compose/src/main/kotlin/com/yandex/div/compose/context/DivViewContext.kt",
|
||||
"client/android/compose/src/main/kotlin/com/yandex/div/compose/context/DivViewContextStorage.kt":"divkit/public/client/android/compose/src/main/kotlin/com/yandex/div/compose/context/DivViewContextStorage.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",
|
||||
@@ -634,7 +638,6 @@
|
||||
"client/android/compose/src/main/kotlin/com/yandex/div/compose/utils/UnitConversions.kt":"divkit/public/client/android/compose/src/main/kotlin/com/yandex/div/compose/utils/UnitConversions.kt",
|
||||
"client/android/compose/src/main/kotlin/com/yandex/div/compose/variables/DivVariableAdapter.kt":"divkit/public/client/android/compose/src/main/kotlin/com/yandex/div/compose/variables/DivVariableAdapter.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/DivLocalContext.kt":"divkit/public/client/android/compose/src/main/kotlin/com/yandex/div/compose/views/DivLocalContext.kt",
|
||||
"client/android/compose/src/main/kotlin/com/yandex/div/compose/views/DivSeparatorView.kt":"divkit/public/client/android/compose/src/main/kotlin/com/yandex/div/compose/views/DivSeparatorView.kt",
|
||||
"client/android/compose/src/main/kotlin/com/yandex/div/compose/views/DivTextView.kt":"divkit/public/client/android/compose/src/main/kotlin/com/yandex/div/compose/views/DivTextView.kt",
|
||||
"client/android/compose/src/main/kotlin/com/yandex/div/compose/views/container/ContainerAlignment.kt":"divkit/public/client/android/compose/src/main/kotlin/com/yandex/div/compose/views/container/ContainerAlignment.kt",
|
||||
@@ -659,6 +662,7 @@
|
||||
"client/android/compose/src/main/kotlin/com/yandex/div/compose/views/modifiers/BorderModifiers.kt":"divkit/public/client/android/compose/src/main/kotlin/com/yandex/div/compose/views/modifiers/BorderModifiers.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/DivViewRecompositionTest.kt":"divkit/public/client/android/compose/src/test/kotlin/com/yandex/div/compose/DivViewRecompositionTest.kt",
|
||||
"client/android/compose/src/test/kotlin/com/yandex/div/compose/DivViewTest.kt":"divkit/public/client/android/compose/src/test/kotlin/com/yandex/div/compose/DivViewTest.kt",
|
||||
"client/android/compose/src/test/kotlin/com/yandex/div/compose/DivViewWithFunctionsTest.kt":"divkit/public/client/android/compose/src/test/kotlin/com/yandex/div/compose/DivViewWithFunctionsTest.kt",
|
||||
"client/android/compose/src/test/kotlin/com/yandex/div/compose/DivViewWithTriggersTest.kt":"divkit/public/client/android/compose/src/test/kotlin/com/yandex/div/compose/DivViewWithTriggersTest.kt",
|
||||
|
||||
@@ -3,13 +3,18 @@ package com.yandex.div.compose
|
||||
import android.content.ContextWrapper
|
||||
import androidx.annotation.MainThread
|
||||
import androidx.annotation.VisibleForTesting
|
||||
import com.yandex.div.compose.context.DivLocalContext
|
||||
import com.yandex.div.compose.context.DivViewContext
|
||||
import com.yandex.div.compose.dagger.DivContextComponent
|
||||
import com.yandex.div.compose.internal.DivDebugFeatures
|
||||
import com.yandex.div.compose.views.DivLocalContext
|
||||
import com.yandex.div.core.annotations.InternalApi
|
||||
import com.yandex.div.core.annotations.PublicApi
|
||||
import com.yandex.div.core.expression.variables.DivVariableController
|
||||
import com.yandex.div.evaluable.function.GeneratedBuiltinFunctionProvider
|
||||
import com.yandex.div.internal.expressions.FunctionProviderDecorator
|
||||
import com.yandex.div.internal.expressions.toLocalFunctions
|
||||
import com.yandex.div2.DivBase
|
||||
import com.yandex.div2.DivData
|
||||
import com.yandex.div2.DivTrigger
|
||||
import com.yandex.div2.DivVariable
|
||||
import javax.inject.Inject
|
||||
@@ -32,7 +37,58 @@ class DivContext @Inject @MainThread internal constructor(
|
||||
val debugFeatures: DivDebugFeatures
|
||||
get() = component.debugFeatures
|
||||
|
||||
internal fun createLocalContext(
|
||||
/**
|
||||
* Removes [DivView] context associated with the given [com.yandex.div2.DivData].
|
||||
*/
|
||||
fun clearViewContext(data: DivData) {
|
||||
component.viewContextStorage.remove(data)
|
||||
}
|
||||
|
||||
internal fun getViewContext(data: DivData): DivViewContext {
|
||||
component.viewContextStorage.get(data)?.let {
|
||||
return it
|
||||
}
|
||||
|
||||
val baseFunctionProvider = FunctionProviderDecorator(GeneratedBuiltinFunctionProvider)
|
||||
val functions = data.functions.orEmpty().toLocalFunctions()
|
||||
return DivViewContext(
|
||||
rootLocalContext = createLocalContext(
|
||||
variableController = DivVariableController(component.variableController),
|
||||
functionProvider = baseFunctionProvider + functions,
|
||||
triggers = data.variableTriggers.orEmpty(),
|
||||
variables = data.variables.orEmpty()
|
||||
)
|
||||
).also {
|
||||
component.viewContextStorage.put(data, it)
|
||||
}
|
||||
}
|
||||
|
||||
internal fun getLocalContext(
|
||||
data: DivBase,
|
||||
viewContext: DivViewContext,
|
||||
parentContext: DivLocalContext
|
||||
): DivLocalContext {
|
||||
viewContext.localContextStorage.get(data)?.let {
|
||||
return it
|
||||
}
|
||||
|
||||
val functions = data.functions.orEmpty().toLocalFunctions()
|
||||
val variables = data.variables.orEmpty()
|
||||
return createLocalContext(
|
||||
variableController = if (variables.isEmpty()) {
|
||||
parentContext.variableController
|
||||
} else {
|
||||
DivVariableController(parentContext.variableController)
|
||||
},
|
||||
functionProvider = parentContext.functionProvider + functions,
|
||||
triggers = data.variableTriggers.orEmpty(),
|
||||
variables = variables
|
||||
).also {
|
||||
viewContext.localContextStorage.put(data, it)
|
||||
}
|
||||
}
|
||||
|
||||
private fun createLocalContext(
|
||||
variableController: DivVariableController,
|
||||
functionProvider: FunctionProviderDecorator,
|
||||
triggers: List<DivTrigger>,
|
||||
|
||||
@@ -1,10 +1,14 @@
|
||||
package com.yandex.div.compose
|
||||
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.runtime.CompositionLocalProvider
|
||||
import androidx.compose.ui.Modifier
|
||||
import com.yandex.div.compose.context.LocalDivContext
|
||||
import com.yandex.div.compose.context.LocalDivViewContext
|
||||
import com.yandex.div.compose.triggers.observe
|
||||
import com.yandex.div.compose.utils.divContext
|
||||
import com.yandex.div.compose.utils.reporter
|
||||
import com.yandex.div.compose.views.DivBlockView
|
||||
import com.yandex.div.compose.views.WithLocalDivContext
|
||||
import com.yandex.div2.DivData
|
||||
|
||||
/**
|
||||
@@ -39,7 +43,12 @@ fun DivView(
|
||||
return
|
||||
}
|
||||
|
||||
WithLocalDivContext(data) {
|
||||
val viewContext = divContext.getViewContext(data)
|
||||
viewContext.rootLocalContext.triggerStorage.observe()
|
||||
CompositionLocalProvider(
|
||||
LocalDivViewContext provides viewContext,
|
||||
LocalDivContext provides viewContext.rootLocalContext
|
||||
) {
|
||||
DivBlockView(
|
||||
data = div,
|
||||
modifier = modifier
|
||||
|
||||
+47
@@ -0,0 +1,47 @@
|
||||
package com.yandex.div.compose.context
|
||||
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.runtime.CompositionLocalProvider
|
||||
import androidx.compose.runtime.compositionLocalOf
|
||||
import com.yandex.div.compose.DivException
|
||||
import com.yandex.div.compose.actions.DivActionHandlingContext
|
||||
import com.yandex.div.compose.dagger.DivLocalScope
|
||||
import com.yandex.div.compose.triggers.DivTriggerStorage
|
||||
import com.yandex.div.compose.triggers.observe
|
||||
import com.yandex.div.compose.utils.divContext
|
||||
import com.yandex.div.core.expression.variables.DivVariableController
|
||||
import com.yandex.div.internal.expressions.FunctionProviderDecorator
|
||||
import com.yandex.div.json.expressions.ExpressionResolver
|
||||
import com.yandex.div2.DivBase
|
||||
import javax.inject.Inject
|
||||
|
||||
@DivLocalScope
|
||||
internal class DivLocalContext @Inject constructor(
|
||||
val actionHandlingContext: DivActionHandlingContext,
|
||||
val expressionResolver: ExpressionResolver,
|
||||
val functionProvider: FunctionProviderDecorator,
|
||||
val triggerStorage: DivTriggerStorage,
|
||||
val variableController: DivVariableController
|
||||
)
|
||||
|
||||
internal val LocalDivContext = compositionLocalOf<DivLocalContext> {
|
||||
throw DivException("DivLocalContext not provided")
|
||||
}
|
||||
|
||||
@Composable
|
||||
internal fun WithLocalDivContext(data: DivBase, content: @Composable () -> Unit) {
|
||||
val functions = data.functions.orEmpty()
|
||||
val variables = data.variables.orEmpty()
|
||||
val triggers = data.variableTriggers.orEmpty()
|
||||
if (functions.isEmpty() && variables.isEmpty() && triggers.isEmpty()) {
|
||||
return content()
|
||||
}
|
||||
|
||||
val localContext = divContext.getLocalContext(
|
||||
data,
|
||||
viewContext = LocalDivViewContext.current,
|
||||
parentContext = LocalDivContext.current
|
||||
)
|
||||
localContext.triggerStorage.observe()
|
||||
CompositionLocalProvider(LocalDivContext provides localContext, content)
|
||||
}
|
||||
+15
@@ -0,0 +1,15 @@
|
||||
package com.yandex.div.compose.context
|
||||
|
||||
import com.yandex.div2.DivBase
|
||||
|
||||
internal class DivLocalContextStorage() {
|
||||
private val items = mutableMapOf<DivBase, DivLocalContext>()
|
||||
|
||||
fun get(div: DivBase): DivLocalContext? {
|
||||
return items[div]
|
||||
}
|
||||
|
||||
fun put(div: DivBase, context: DivLocalContext) {
|
||||
items[div] = context
|
||||
}
|
||||
}
|
||||
+17
@@ -0,0 +1,17 @@
|
||||
package com.yandex.div.compose.context
|
||||
|
||||
import androidx.compose.runtime.compositionLocalOf
|
||||
import com.yandex.div.compose.DivException
|
||||
|
||||
internal class DivViewContext(
|
||||
val rootLocalContext: DivLocalContext
|
||||
) {
|
||||
/**
|
||||
* Stores [DivLocalContext]s for view elements.
|
||||
*/
|
||||
val localContextStorage = DivLocalContextStorage()
|
||||
}
|
||||
|
||||
internal val LocalDivViewContext = compositionLocalOf<DivViewContext> {
|
||||
throw DivException("DivViewContext not provided")
|
||||
}
|
||||
+22
@@ -0,0 +1,22 @@
|
||||
package com.yandex.div.compose.context
|
||||
|
||||
import com.yandex.div.compose.dagger.DivContextScope
|
||||
import com.yandex.div2.DivData
|
||||
import javax.inject.Inject
|
||||
|
||||
@DivContextScope
|
||||
internal class DivViewContextStorage @Inject constructor() {
|
||||
private val items = mutableMapOf<DivData, DivViewContext>()
|
||||
|
||||
fun get(data: DivData): DivViewContext? {
|
||||
return items[data]
|
||||
}
|
||||
|
||||
fun put(data: DivData, context: DivViewContext) {
|
||||
items[data] = context
|
||||
}
|
||||
|
||||
fun remove(data: DivData) {
|
||||
items.remove(data)
|
||||
}
|
||||
}
|
||||
+3
-1
@@ -5,8 +5,9 @@ import coil3.ImageLoader
|
||||
import com.yandex.div.compose.DivComposeConfiguration
|
||||
import com.yandex.div.compose.DivReporter
|
||||
import com.yandex.div.compose.actions.DivActionHandler
|
||||
import com.yandex.div.core.expression.variables.DivVariableController
|
||||
import com.yandex.div.compose.context.DivViewContextStorage
|
||||
import com.yandex.div.compose.internal.DivDebugFeatures
|
||||
import com.yandex.div.core.expression.variables.DivVariableController
|
||||
import com.yandex.yatagan.BindsInstance
|
||||
import com.yandex.yatagan.Component
|
||||
import javax.inject.Named
|
||||
@@ -25,6 +26,7 @@ internal interface DivContextComponent {
|
||||
val debugFeatures: DivDebugFeatures
|
||||
val imageLoader: ImageLoader
|
||||
val reporter: DivReporter
|
||||
val viewContextStorage: DivViewContextStorage
|
||||
|
||||
@get:Named(Names.HOST_VARIABLES)
|
||||
val variableController: DivVariableController
|
||||
|
||||
+1
-1
@@ -2,7 +2,7 @@ package com.yandex.div.compose.dagger
|
||||
|
||||
import com.yandex.div.compose.triggers.DivTriggerStorage
|
||||
import com.yandex.div.compose.variables.DivVariableAdapter
|
||||
import com.yandex.div.compose.views.DivLocalContext
|
||||
import com.yandex.div.compose.context.DivLocalContext
|
||||
import com.yandex.div.core.expression.variables.DivVariableController
|
||||
import com.yandex.div.internal.expressions.FunctionProviderDecorator
|
||||
import com.yandex.yatagan.BindsInstance
|
||||
|
||||
+23
-8
@@ -1,26 +1,41 @@
|
||||
package com.yandex.div.compose.internal
|
||||
|
||||
import com.yandex.div.compose.actions.DivActionHandler
|
||||
import com.yandex.div.compose.context.DivViewContextStorage
|
||||
import com.yandex.div.compose.dagger.DivContextScope
|
||||
import com.yandex.div.compose.views.DivLocalContext
|
||||
import com.yandex.div.core.annotations.InternalApi
|
||||
import com.yandex.div.json.expressions.ExpressionResolver
|
||||
import com.yandex.div2.DivAction
|
||||
import com.yandex.div2.DivData
|
||||
import javax.inject.Inject
|
||||
|
||||
/**
|
||||
* Provides access to the features not intended to be used in production environment.
|
||||
*/
|
||||
@DivContextScope
|
||||
@InternalApi
|
||||
class DivDebugFeatures @Inject internal constructor(
|
||||
private val actionHandler: DivActionHandler
|
||||
private val actionHandler: DivActionHandler,
|
||||
private val viewContextStorage: DivViewContextStorage
|
||||
) {
|
||||
internal var lastViewLocalContext: DivLocalContext? = null
|
||||
|
||||
val expressionResolver: ExpressionResolver?
|
||||
get() = lastViewLocalContext?.expressionResolver
|
||||
/**
|
||||
* Returns [ExpressionResolver] associated with the given [DivData].
|
||||
*/
|
||||
fun getExpressionResolver(data: DivData): ExpressionResolver? {
|
||||
return viewContextStorage.get(data)?.rootLocalContext?.expressionResolver
|
||||
}
|
||||
|
||||
fun performAction(action: DivAction) {
|
||||
lastViewLocalContext?.let {
|
||||
actionHandler.handle(context = it.actionHandlingContext, action = action)
|
||||
/**
|
||||
* Performs action in the context of [com.yandex.div.compose.DivView] associated with
|
||||
* the given [DivData].
|
||||
*/
|
||||
fun performAction(data: DivData, action: DivAction) {
|
||||
viewContextStorage.get(data)?.let {
|
||||
actionHandler.handle(
|
||||
context = it.rootLocalContext.actionHandlingContext,
|
||||
action = action
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
+45
-8
@@ -1,5 +1,7 @@
|
||||
package com.yandex.div.compose.triggers
|
||||
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.runtime.DisposableEffect
|
||||
import com.yandex.div.compose.actions.DivActionHandler
|
||||
import com.yandex.div.compose.actions.DivActionHandlingContext
|
||||
import com.yandex.div.compose.dagger.DivLocalScope
|
||||
@@ -19,18 +21,13 @@ internal class DivTriggerStorage @Inject constructor(
|
||||
|
||||
private class Item(
|
||||
private val trigger: DivTrigger,
|
||||
expressionResolver: ExpressionResolver,
|
||||
private val expressionResolver: ExpressionResolver,
|
||||
private val handleActions: (actions: List<DivAction>) -> Unit
|
||||
) {
|
||||
private val mode = trigger.mode.evaluate(expressionResolver)
|
||||
private val conditionSubscription: Disposable
|
||||
|
||||
private var lastCondition: Boolean = false
|
||||
|
||||
init {
|
||||
conditionSubscription = trigger.condition
|
||||
.observeAndGet(expressionResolver, this::onConditionChanged)
|
||||
}
|
||||
private var conditionSubscription: Disposable? = null
|
||||
|
||||
private fun onConditionChanged(condition: Boolean) {
|
||||
when (mode) {
|
||||
@@ -47,12 +44,42 @@ internal class DivTriggerStorage @Inject constructor(
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fun startObserving() {
|
||||
conditionSubscription = trigger.condition
|
||||
.observeAndGet(expressionResolver, this::onConditionChanged)
|
||||
}
|
||||
|
||||
fun stopObserving() {
|
||||
conditionSubscription?.close()
|
||||
conditionSubscription = null
|
||||
}
|
||||
}
|
||||
|
||||
private val items = mutableListOf<Item>()
|
||||
private var isActive = true
|
||||
|
||||
fun add(trigger: DivTrigger) {
|
||||
items.add(Item(trigger, expressionResolver, this::handle))
|
||||
val item = Item(trigger, expressionResolver, this::handle)
|
||||
items.add(item)
|
||||
|
||||
if (isActive) {
|
||||
item.startObserving()
|
||||
}
|
||||
}
|
||||
|
||||
fun startObserving() {
|
||||
if (!isActive) {
|
||||
items.forEach { it.startObserving() }
|
||||
isActive = true
|
||||
}
|
||||
}
|
||||
|
||||
fun stopObserving() {
|
||||
if (isActive) {
|
||||
items.forEach { it.stopObserving() }
|
||||
isActive = false
|
||||
}
|
||||
}
|
||||
|
||||
private fun handle(actions: List<DivAction>) {
|
||||
@@ -61,3 +88,13 @@ internal class DivTriggerStorage @Inject constructor(
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Composable
|
||||
internal fun DivTriggerStorage.observe() {
|
||||
DisposableEffect(this) {
|
||||
startObserving()
|
||||
onDispose {
|
||||
stopObserving()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -6,7 +6,7 @@ import coil3.ImageLoader
|
||||
import com.yandex.div.compose.DivContext
|
||||
import com.yandex.div.compose.DivReporter
|
||||
import com.yandex.div.compose.actions.DivActionHandler
|
||||
import com.yandex.div.compose.views.LocalDivContext
|
||||
import com.yandex.div.compose.context.LocalDivContext
|
||||
import com.yandex.div.json.expressions.ExpressionResolver
|
||||
|
||||
internal val divContext: DivContext
|
||||
|
||||
@@ -2,6 +2,7 @@ package com.yandex.div.compose.views
|
||||
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.ui.Modifier
|
||||
import com.yandex.div.compose.context.WithLocalDivContext
|
||||
import com.yandex.div.compose.views.container.DivContainerView
|
||||
import com.yandex.div.compose.views.gallery.DivGalleryView
|
||||
import com.yandex.div.compose.views.image.DivImageView
|
||||
|
||||
-75
@@ -1,75 +0,0 @@
|
||||
package com.yandex.div.compose.views
|
||||
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.runtime.CompositionLocalProvider
|
||||
import androidx.compose.runtime.compositionLocalOf
|
||||
import androidx.compose.runtime.remember
|
||||
import com.yandex.div.compose.DivException
|
||||
import com.yandex.div.compose.actions.DivActionHandlingContext
|
||||
import com.yandex.div.compose.dagger.DivLocalScope
|
||||
import com.yandex.div.compose.utils.divContext
|
||||
import com.yandex.div.core.expression.variables.DivVariableController
|
||||
import com.yandex.div.evaluable.function.GeneratedBuiltinFunctionProvider
|
||||
import com.yandex.div.internal.expressions.FunctionProviderDecorator
|
||||
import com.yandex.div.internal.expressions.toLocalFunctions
|
||||
import com.yandex.div.json.expressions.ExpressionResolver
|
||||
import com.yandex.div2.DivBase
|
||||
import com.yandex.div2.DivData
|
||||
import javax.inject.Inject
|
||||
|
||||
@DivLocalScope
|
||||
internal class DivLocalContext @Inject constructor(
|
||||
val actionHandlingContext: DivActionHandlingContext,
|
||||
val expressionResolver: ExpressionResolver,
|
||||
val functionProvider: FunctionProviderDecorator,
|
||||
val variableController: DivVariableController
|
||||
)
|
||||
|
||||
internal val LocalDivContext = compositionLocalOf<DivLocalContext> {
|
||||
throw DivException("DivLocalContext not provided")
|
||||
}
|
||||
|
||||
@Composable
|
||||
internal fun WithLocalDivContext(data: DivData, content: @Composable () -> Unit) {
|
||||
val divContext = divContext
|
||||
val localContext = remember(data) {
|
||||
val baseFunctionProvider = FunctionProviderDecorator(GeneratedBuiltinFunctionProvider)
|
||||
val functions = data.functions.orEmpty().toLocalFunctions()
|
||||
divContext.createLocalContext(
|
||||
variableController = DivVariableController(divContext.component.variableController),
|
||||
functionProvider = baseFunctionProvider + functions,
|
||||
triggers = data.variableTriggers.orEmpty(),
|
||||
variables = data.variables.orEmpty()
|
||||
).also {
|
||||
divContext.debugFeatures.lastViewLocalContext = it
|
||||
}
|
||||
}
|
||||
CompositionLocalProvider(LocalDivContext provides localContext, content)
|
||||
}
|
||||
|
||||
@Composable
|
||||
internal fun WithLocalDivContext(data: DivBase, content: @Composable () -> Unit) {
|
||||
val functions = data.functions.orEmpty()
|
||||
val variables = data.variables.orEmpty()
|
||||
val triggers = data.variableTriggers.orEmpty()
|
||||
if (functions.isEmpty() && variables.isEmpty() && triggers.isEmpty()) {
|
||||
return content()
|
||||
}
|
||||
|
||||
val divContext = divContext
|
||||
val localContext = LocalDivContext.current
|
||||
val newLocalContext = remember(data) {
|
||||
val variableController = if (variables.isEmpty()) {
|
||||
localContext.variableController
|
||||
} else {
|
||||
DivVariableController(localContext.variableController)
|
||||
}
|
||||
divContext.createLocalContext(
|
||||
variableController = variableController,
|
||||
functionProvider = localContext.functionProvider + functions.toLocalFunctions(),
|
||||
triggers = triggers,
|
||||
variables = variables
|
||||
)
|
||||
}
|
||||
CompositionLocalProvider(LocalDivContext provides newLocalContext, content)
|
||||
}
|
||||
+1
-1
@@ -16,7 +16,7 @@ import com.yandex.div.compose.utils.observedFloatValue
|
||||
import com.yandex.div.compose.utils.observedIntValue
|
||||
import com.yandex.div.compose.utils.observedValue
|
||||
import com.yandex.div.compose.utils.reporter
|
||||
import com.yandex.div.compose.views.LocalDivContext
|
||||
import com.yandex.div.compose.context.LocalDivContext
|
||||
import com.yandex.div2.Div
|
||||
import com.yandex.div2.DivAction
|
||||
import com.yandex.div2.DivAnimation
|
||||
|
||||
+177
@@ -0,0 +1,177 @@
|
||||
package com.yandex.div.compose
|
||||
|
||||
import android.view.View
|
||||
import androidx.activity.ComponentActivity
|
||||
import androidx.compose.runtime.State
|
||||
import androidx.compose.runtime.mutableStateOf
|
||||
import androidx.compose.ui.platform.ComposeView
|
||||
import androidx.compose.ui.test.assertIsDisplayed
|
||||
import androidx.compose.ui.test.assertTextEquals
|
||||
import androidx.compose.ui.test.junit4.createAndroidComposeRule
|
||||
import androidx.compose.ui.test.onNodeWithTag
|
||||
import androidx.compose.ui.test.onNodeWithText
|
||||
import androidx.compose.ui.test.performClick
|
||||
import androidx.test.ext.junit.runners.AndroidJUnit4
|
||||
import com.yandex.div.test.data.action
|
||||
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.text
|
||||
import com.yandex.div.test.data.typedValue
|
||||
import com.yandex.div.test.data.variable
|
||||
import com.yandex.div2.DivData
|
||||
import org.junit.Before
|
||||
import org.junit.Rule
|
||||
import org.junit.Test
|
||||
import org.junit.runner.RunWith
|
||||
|
||||
@RunWith(AndroidJUnit4::class)
|
||||
class DivViewRecompositionTest {
|
||||
|
||||
@get:Rule
|
||||
val rule = createAndroidComposeRule<ComponentActivity>()
|
||||
|
||||
private val mutableData = mutableStateOf(data(text(text = "")))
|
||||
|
||||
private val activity: ComponentActivity
|
||||
get() = rule.activity
|
||||
|
||||
private lateinit var divContext: DivContext
|
||||
|
||||
@Before
|
||||
fun setup() {
|
||||
divContext = DivComposeConfiguration(
|
||||
reporter = TestReporter()
|
||||
).createContext(baseContext = activity)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `content changes when data is updated`() {
|
||||
mutableData.value = data(text(text = "Data 1"))
|
||||
|
||||
setContent(mutableData)
|
||||
|
||||
rule.onNodeWithText("Data 1").assertIsDisplayed()
|
||||
|
||||
mutableData.value = data(text(text = "Data 2"))
|
||||
|
||||
rule.onNodeWithText("Data 2").assertIsDisplayed()
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `new variables are used when data is updated`() {
|
||||
mutableData.value = data(
|
||||
text(text = expression("Data @{counter}")),
|
||||
variables = listOf(variable("counter", 1))
|
||||
)
|
||||
|
||||
setContent(mutableData)
|
||||
|
||||
rule.onNodeWithText("Data 1").assertIsDisplayed()
|
||||
|
||||
mutableData.value = data(
|
||||
text(text = expression("Data @{counter}")),
|
||||
variables = listOf(variable("counter", 2))
|
||||
)
|
||||
|
||||
rule.onNodeWithText("Data 2").assertIsDisplayed()
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `variable values are restored when data is switched back`() {
|
||||
val data = data(
|
||||
text(
|
||||
action = action(
|
||||
typed = setVariableAction(name = "counter", value = typedValue(5))
|
||||
),
|
||||
id = "button",
|
||||
text = expression("counter = @{counter}")
|
||||
),
|
||||
variables = listOf(variable("counter", 1))
|
||||
)
|
||||
mutableData.value = data
|
||||
|
||||
setContent(mutableData)
|
||||
|
||||
rule.onNodeWithTag("button").apply {
|
||||
assertTextEquals("counter = 1")
|
||||
performClick()
|
||||
assertTextEquals("counter = 5")
|
||||
}
|
||||
|
||||
mutableData.value = data(text(text = "New Data"))
|
||||
|
||||
rule.onNodeWithText("New Data").assertIsDisplayed()
|
||||
|
||||
mutableData.value = data
|
||||
|
||||
rule.onNodeWithTag("button").assertTextEquals("counter = 5")
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `variable values are restored when ComposeView is recreated with the same context`() {
|
||||
val data = data(
|
||||
text(
|
||||
action = action(
|
||||
typed = setVariableAction(name = "counter", value = typedValue(5))
|
||||
),
|
||||
id = "button",
|
||||
text = expression("counter = @{counter}")
|
||||
),
|
||||
variables = listOf(variable("counter", 1))
|
||||
)
|
||||
|
||||
setContent(data)
|
||||
|
||||
rule.onNodeWithTag("button").apply {
|
||||
assertTextEquals("counter = 1")
|
||||
performClick()
|
||||
assertTextEquals("counter = 5")
|
||||
}
|
||||
|
||||
activity.setContentView(View(activity))
|
||||
setContent(data)
|
||||
|
||||
rule.onNodeWithTag("button").assertTextEquals("counter = 5")
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `variable values are not restored when ComposeView is recreated and context is cleared`() {
|
||||
val data = data(
|
||||
text(
|
||||
action = action(
|
||||
typed = setVariableAction(name = "counter", value = typedValue(5))
|
||||
),
|
||||
id = "button",
|
||||
text = expression("counter = @{counter}")
|
||||
),
|
||||
variables = listOf(variable("counter", 1))
|
||||
)
|
||||
|
||||
setContent(data)
|
||||
|
||||
rule.onNodeWithTag("button").apply {
|
||||
assertTextEquals("counter = 1")
|
||||
performClick()
|
||||
assertTextEquals("counter = 5")
|
||||
}
|
||||
|
||||
activity.setContentView(View(activity))
|
||||
divContext.clearViewContext(data)
|
||||
setContent(data)
|
||||
|
||||
rule.onNodeWithTag("button").assertTextEquals("counter = 1")
|
||||
}
|
||||
|
||||
private fun setContent(data: DivData) = setContent(mutableStateOf(data))
|
||||
|
||||
private fun setContent(data: State<DivData>) {
|
||||
activity.setContentView(
|
||||
ComposeView(divContext).apply {
|
||||
setContent {
|
||||
DivView(data.value)
|
||||
}
|
||||
}
|
||||
)
|
||||
}
|
||||
}
|
||||
@@ -1,7 +1,5 @@
|
||||
package com.yandex.div.compose
|
||||
|
||||
import androidx.compose.runtime.CompositionLocalProvider
|
||||
import androidx.compose.ui.platform.LocalContext
|
||||
import androidx.compose.ui.test.assertIsDisplayed
|
||||
import androidx.compose.ui.test.assertTextEquals
|
||||
import androidx.compose.ui.test.junit4.createComposeRule
|
||||
@@ -150,11 +148,9 @@ class DivViewTest {
|
||||
content: Div,
|
||||
variables: List<DivVariable>? = null
|
||||
) {
|
||||
rule.setContent {
|
||||
val divContext = configuration.createContext(baseContext = LocalContext.current)
|
||||
CompositionLocalProvider(LocalContext provides divContext) {
|
||||
DivView(data = data(content, variables = variables))
|
||||
}
|
||||
}
|
||||
rule.setContent(
|
||||
configuration = configuration,
|
||||
data = data(content, variables = variables)
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
+127
-9
@@ -1,7 +1,10 @@
|
||||
package com.yandex.div.compose
|
||||
|
||||
import android.view.View
|
||||
import androidx.activity.ComponentActivity
|
||||
import androidx.compose.ui.platform.ComposeView
|
||||
import androidx.compose.ui.test.assertTextEquals
|
||||
import androidx.compose.ui.test.junit4.createComposeRule
|
||||
import androidx.compose.ui.test.junit4.createAndroidComposeRule
|
||||
import androidx.compose.ui.test.onNodeWithTag
|
||||
import androidx.test.ext.junit.runners.AndroidJUnit4
|
||||
import com.yandex.div.core.expression.variables.DivVariableController
|
||||
@@ -9,12 +12,18 @@ import com.yandex.div.data.Variable
|
||||
import com.yandex.div.test.data.action
|
||||
import com.yandex.div.test.data.data
|
||||
import com.yandex.div.test.data.expression
|
||||
import com.yandex.div.test.data.intExpression
|
||||
import com.yandex.div.test.data.setVariableAction
|
||||
import com.yandex.div.test.data.text
|
||||
import com.yandex.div.test.data.trigger
|
||||
import com.yandex.div.test.data.typedValue
|
||||
import com.yandex.div.test.data.variable
|
||||
import com.yandex.div2.Div
|
||||
import com.yandex.div2.DivData
|
||||
import com.yandex.div2.DivTrigger
|
||||
import com.yandex.div2.DivVariable
|
||||
import org.junit.Assert.assertEquals
|
||||
import org.junit.Before
|
||||
import org.junit.Rule
|
||||
import org.junit.Test
|
||||
import org.junit.runner.RunWith
|
||||
@@ -23,14 +32,22 @@ import org.junit.runner.RunWith
|
||||
class DivViewWithTriggersTest {
|
||||
|
||||
@get:Rule
|
||||
val rule = createComposeRule()
|
||||
val rule = createAndroidComposeRule<ComponentActivity>()
|
||||
|
||||
private val variableController = DivVariableController()
|
||||
|
||||
private val configuration = DivComposeConfiguration(
|
||||
reporter = TestReporter(),
|
||||
variableController = variableController
|
||||
)
|
||||
private val activity: ComponentActivity
|
||||
get() = rule.activity
|
||||
|
||||
private lateinit var divContext: DivContext
|
||||
|
||||
@Before
|
||||
fun setup() {
|
||||
divContext = DivComposeConfiguration(
|
||||
reporter = TestReporter(),
|
||||
variableController = variableController
|
||||
).createContext(baseContext = activity)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `text changes when trigger is triggered`() {
|
||||
@@ -84,14 +101,115 @@ class DivViewWithTriggersTest {
|
||||
rule.onNodeWithTag("title").assertTextEquals("new text")
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `does not trigger after leaving composition`() {
|
||||
val condition = Variable.BooleanVariable("condition", false)
|
||||
val counter = Variable.IntegerVariable("counter", 1)
|
||||
variableController.declare(condition, counter)
|
||||
|
||||
setContent(
|
||||
text(
|
||||
id = "title",
|
||||
text = expression("counter = @{counter}")
|
||||
),
|
||||
triggers = listOf(
|
||||
trigger(
|
||||
action = action(
|
||||
typed = setVariableAction(
|
||||
name = "counter",
|
||||
value = typedValue(intExpression("@{counter + 1}"))
|
||||
)
|
||||
),
|
||||
condition = "@{condition}"
|
||||
)
|
||||
)
|
||||
)
|
||||
|
||||
activity.setContentView(View(activity))
|
||||
condition.set(true)
|
||||
|
||||
assertEquals(1L, counter.getValue())
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `does not trigger (local) after leaving composition`() {
|
||||
val condition = Variable.BooleanVariable("condition", false)
|
||||
val counter = Variable.IntegerVariable("counter", 1)
|
||||
variableController.declare(condition, counter)
|
||||
|
||||
setContent(
|
||||
text(
|
||||
id = "title",
|
||||
text = expression("counter = @{counter}"),
|
||||
triggers = listOf(
|
||||
trigger(
|
||||
action = action(
|
||||
typed = setVariableAction(
|
||||
name = "counter",
|
||||
value = typedValue(intExpression("@{counter + 1}"))
|
||||
)
|
||||
),
|
||||
condition = "@{condition}"
|
||||
)
|
||||
)
|
||||
)
|
||||
)
|
||||
|
||||
activity.setContentView(View(activity))
|
||||
condition.set(true)
|
||||
|
||||
assertEquals(1L, counter.getValue())
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `does not trigger when reentering composition and condition is not changed`() {
|
||||
val condition = Variable.BooleanVariable("condition", false)
|
||||
val counter = Variable.IntegerVariable("counter", 1)
|
||||
variableController.declare(condition, counter)
|
||||
|
||||
val data = data(
|
||||
text(
|
||||
id = "title",
|
||||
text = expression("counter = @{counter}")
|
||||
),
|
||||
triggers = listOf(
|
||||
trigger(
|
||||
action = action(
|
||||
typed = setVariableAction(
|
||||
name = "counter",
|
||||
value = typedValue(intExpression("@{counter + 1}"))
|
||||
)
|
||||
),
|
||||
condition = "@{condition}"
|
||||
)
|
||||
)
|
||||
)
|
||||
setContent(data)
|
||||
|
||||
condition.set(true)
|
||||
assertEquals(2L, counter.getValue())
|
||||
|
||||
activity.setContentView(View(activity))
|
||||
setContent(data)
|
||||
|
||||
assertEquals(2L, counter.getValue())
|
||||
}
|
||||
|
||||
private fun setContent(
|
||||
content: Div,
|
||||
triggers: List<DivTrigger>? = null,
|
||||
variables: List<DivVariable>? = null
|
||||
) {
|
||||
rule.setContent(
|
||||
configuration = configuration,
|
||||
data = data(content, triggers = triggers, variables = variables)
|
||||
setContent(data(content, triggers = triggers, variables = variables))
|
||||
}
|
||||
|
||||
private fun setContent(data: DivData) {
|
||||
activity.setContentView(
|
||||
ComposeView(divContext).apply {
|
||||
setContent {
|
||||
DivView(data)
|
||||
}
|
||||
}
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -45,11 +45,11 @@ class IntegrationTest(testCaseParsingResult: ParsingResult<IntegrationTestCase>)
|
||||
}
|
||||
|
||||
testCase.parseActions().forEach {
|
||||
divContext.debugFeatures.performAction(it)
|
||||
divContext.debugFeatures.performAction(data = divData, action = it)
|
||||
}
|
||||
|
||||
testCase.checkResult(
|
||||
expressionResolver = divContext.debugFeatures.expressionResolver!!
|
||||
expressionResolver = divContext.debugFeatures.getExpressionResolver(divData)!!
|
||||
)
|
||||
}
|
||||
|
||||
|
||||
+32
@@ -118,6 +118,38 @@ class DivTriggerStorageTest {
|
||||
verifyTriggered()
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `not triggered when added after stopObserving() is called`() {
|
||||
triggerStorage.stopObserving()
|
||||
triggerStorage.add(trigger(action = action, condition = "@{true}"))
|
||||
|
||||
verifyNoInteractions(actionHandler)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `triggered when startObserving() is called`() {
|
||||
triggerStorage.stopObserving()
|
||||
triggerStorage.add(trigger(action = action, condition = "@{true}"))
|
||||
|
||||
verifyNoInteractions(actionHandler)
|
||||
|
||||
triggerStorage.startObserving()
|
||||
|
||||
verifyTriggered()
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `not triggered when startObserving() is called and was triggered before`() {
|
||||
triggerStorage.add(trigger(action = action, condition = "@{true}"))
|
||||
|
||||
verifyTriggered()
|
||||
|
||||
triggerStorage.stopObserving()
|
||||
triggerStorage.startObserving()
|
||||
|
||||
verifyNoInteractions(actionHandler)
|
||||
}
|
||||
|
||||
private fun verifyTriggered() {
|
||||
verify(actionHandler).handle(actionHandlingContext, action)
|
||||
clearInvocations(actionHandler)
|
||||
|
||||
+3
-2
@@ -10,8 +10,8 @@ import androidx.compose.runtime.setValue
|
||||
import androidx.compose.ui.test.junit4.createComposeRule
|
||||
import androidx.test.ext.junit.runners.AndroidJUnit4
|
||||
import com.yandex.div.compose.createExpressionResolver
|
||||
import com.yandex.div.compose.views.DivLocalContext
|
||||
import com.yandex.div.compose.views.LocalDivContext
|
||||
import com.yandex.div.compose.context.DivLocalContext
|
||||
import com.yandex.div.compose.context.LocalDivContext
|
||||
import com.yandex.div.core.expression.variables.DivVariableController
|
||||
import com.yandex.div.data.Variable
|
||||
import com.yandex.div.json.expressions.Expression
|
||||
@@ -36,6 +36,7 @@ class ExpressionUtilsTest {
|
||||
variableController = variableController
|
||||
),
|
||||
functionProvider = mock(),
|
||||
triggerStorage = mock(),
|
||||
variableController = variableController
|
||||
)
|
||||
|
||||
|
||||
+3
-2
@@ -28,6 +28,7 @@ adb shell am start -n com.yandex.divkit.demo/com.yandex.divkit.demo.screenshot.D
|
||||
class DivComposeScreenshotActivity : ComponentActivity() {
|
||||
|
||||
private lateinit var divContext: DivContext
|
||||
private lateinit var data: DivData
|
||||
|
||||
val imageLoadingTracker = ComposeImageLoadingTracker()
|
||||
|
||||
@@ -55,7 +56,7 @@ class DivComposeScreenshotActivity : ComponentActivity() {
|
||||
val environment = DivParsingEnvironment(ParsingErrorLogger.ASSERT).apply {
|
||||
if (templatesJson != null) parseTemplates(templatesJson)
|
||||
}
|
||||
val data = DivData(environment, json.getJSONObject("card"))
|
||||
data = DivData(environment, json.getJSONObject("card"))
|
||||
|
||||
val view = ComposeView(divContext).apply {
|
||||
id = R.id.screenshot_view
|
||||
@@ -73,7 +74,7 @@ class DivComposeScreenshotActivity : ComponentActivity() {
|
||||
|
||||
fun performActions(actions: List<DivAction>) {
|
||||
actions.forEach {
|
||||
divContext.debugFeatures.performAction(it)
|
||||
divContext.debugFeatures.performAction(data = data, action = it)
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
+5
@@ -1,5 +1,6 @@
|
||||
package com.yandex.div.test.data
|
||||
|
||||
import com.yandex.div.json.expressions.Expression
|
||||
import com.yandex.div2.ArrayValue
|
||||
import com.yandex.div2.ColorValue
|
||||
import com.yandex.div2.DivTypedValue
|
||||
@@ -15,6 +16,10 @@ fun typedValue(value: Long): DivTypedValue {
|
||||
return DivTypedValue.Integer(IntegerValue(value = constant(value)))
|
||||
}
|
||||
|
||||
fun typedValue(value: Expression<Long>): DivTypedValue {
|
||||
return DivTypedValue.Integer(IntegerValue(value = value))
|
||||
}
|
||||
|
||||
fun typedValue(value: JSONArray): DivTypedValue {
|
||||
return DivTypedValue.Array(ArrayValue(value = constant(value)))
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user