mirror of
https://github.com/divkit/divkit.git
synced 2026-05-07 20:02:32 +00:00
Added DivViewComponent
commit_hash:145be58fb45e34d115aeae19a4e8ba9f4ce4b96a
This commit is contained in:
@@ -3,7 +3,6 @@ package com.yandex.div.compose
|
||||
import android.content.Context
|
||||
import android.content.ContextWrapper
|
||||
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.dagger.`Yatagan$DivContextComponent`
|
||||
@@ -11,14 +10,7 @@ import com.yandex.div.compose.internal.DivDebugConfiguration
|
||||
import com.yandex.div.compose.internal.DivDebugFeatures
|
||||
import com.yandex.div.core.annotations.ExperimentalApi
|
||||
import com.yandex.div.core.annotations.InternalApi
|
||||
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
|
||||
|
||||
/**
|
||||
* An implementation of [android.content.Context] that must be used for composing [DivView]s.
|
||||
@@ -70,68 +62,13 @@ class DivContext private constructor(
|
||||
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()
|
||||
)
|
||||
data = data,
|
||||
component = component.viewComponent().build()
|
||||
).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>,
|
||||
variables: List<DivVariable>,
|
||||
): DivLocalContext {
|
||||
val localComponent = component.localComponent()
|
||||
.functionProvider(functionProvider)
|
||||
.variableController(variableController)
|
||||
.build()
|
||||
|
||||
variables.forEach { variableData ->
|
||||
localComponent.variableAdapter.convert(variableData)?.let {
|
||||
variableController.declare(it)
|
||||
}
|
||||
}
|
||||
|
||||
triggers.forEach {
|
||||
localComponent.triggerStorage.add(it)
|
||||
}
|
||||
|
||||
return localComponent.context
|
||||
}
|
||||
}
|
||||
|
||||
private fun createComponent(
|
||||
@@ -139,9 +76,9 @@ private fun createComponent(
|
||||
configuration: DivComposeConfiguration,
|
||||
debugConfiguration: DivDebugConfiguration
|
||||
): DivContextComponent {
|
||||
return `Yatagan$DivContextComponent`.builder()
|
||||
.baseContext(baseContext)
|
||||
.configuration(configuration)
|
||||
.debugConfiguration(debugConfiguration)
|
||||
.build()
|
||||
return `Yatagan$DivContextComponent`.builder().build(
|
||||
baseContext = baseContext,
|
||||
configuration = configuration,
|
||||
debugConfiguration = debugConfiguration
|
||||
)
|
||||
}
|
||||
|
||||
@@ -3,8 +3,8 @@ 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.dagger.LocalComponent
|
||||
import com.yandex.div.compose.triggers.observe
|
||||
import com.yandex.div.compose.utils.divContext
|
||||
import com.yandex.div.compose.utils.reporter
|
||||
@@ -46,10 +46,11 @@ fun DivView(
|
||||
}
|
||||
|
||||
val viewContext = divContext.getViewContext(data)
|
||||
viewContext.rootLocalContext.triggerStorage.observe()
|
||||
val localComponent = viewContext.rootLocalComponent
|
||||
localComponent.triggerStorage.observe()
|
||||
CompositionLocalProvider(
|
||||
LocalDivViewContext provides viewContext,
|
||||
LocalDivContext provides viewContext.rootLocalContext
|
||||
LocalComponent provides localComponent
|
||||
) {
|
||||
DivBlockView(
|
||||
data = div,
|
||||
|
||||
+2
-2
@@ -1,7 +1,7 @@
|
||||
package com.yandex.div.compose.actions
|
||||
|
||||
import com.yandex.div.compose.dagger.DivLocalScope
|
||||
import com.yandex.div.core.annotations.ExperimentalApi
|
||||
import com.yandex.div.core.annotations.Mockable
|
||||
import com.yandex.div.json.expressions.ExpressionResolver
|
||||
import javax.inject.Inject
|
||||
|
||||
@@ -9,7 +9,7 @@ import javax.inject.Inject
|
||||
* Context passed to action handlers. Provides access to DivKit components
|
||||
* scoped to the element that triggered the action.
|
||||
*/
|
||||
@Mockable
|
||||
@DivLocalScope
|
||||
@ExperimentalApi
|
||||
class DivActionHandlingContext @Inject internal constructor(
|
||||
val expressionResolver: ExpressionResolver
|
||||
|
||||
+2
-2
@@ -1,6 +1,6 @@
|
||||
package com.yandex.div.compose.actions
|
||||
|
||||
import com.yandex.div.compose.dagger.DivContextScope
|
||||
import com.yandex.div.compose.dagger.DivViewScope
|
||||
import com.yandex.div2.DivDisappearAction
|
||||
import com.yandex.div2.DivSightAction
|
||||
import com.yandex.div2.DivVisibilityAction
|
||||
@@ -10,7 +10,7 @@ import kotlinx.coroutines.delay
|
||||
import kotlinx.coroutines.launch
|
||||
import javax.inject.Inject
|
||||
|
||||
@DivContextScope
|
||||
@DivViewScope
|
||||
internal class VisibilityActionTracker @Inject constructor(
|
||||
private val actionHandler: DivActionHandler,
|
||||
private val coroutineScope: CoroutineScope
|
||||
|
||||
+19
@@ -0,0 +1,19 @@
|
||||
package com.yandex.div.compose.context
|
||||
|
||||
import com.yandex.div.compose.dagger.DivLocalComponent
|
||||
import com.yandex.div.compose.dagger.DivViewScope
|
||||
import com.yandex.div2.DivBase
|
||||
import javax.inject.Inject
|
||||
|
||||
@DivViewScope
|
||||
internal class DivLocalComponentStorage @Inject constructor() {
|
||||
private val items = mutableMapOf<DivBase, DivLocalComponent>()
|
||||
|
||||
fun get(div: DivBase): DivLocalComponent? {
|
||||
return items[div]
|
||||
}
|
||||
|
||||
fun put(div: DivBase, context: DivLocalComponent) {
|
||||
items[div] = context
|
||||
}
|
||||
}
|
||||
-47
@@ -1,47 +0,0 @@
|
||||
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
@@ -1,15 +0,0 @@
|
||||
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
|
||||
}
|
||||
}
|
||||
+79
-5
@@ -2,14 +2,88 @@ package com.yandex.div.compose.context
|
||||
|
||||
import androidx.compose.runtime.compositionLocalOf
|
||||
import com.yandex.div.compose.DivException
|
||||
import com.yandex.div.compose.actions.VisibilityActionTracker
|
||||
import com.yandex.div.compose.dagger.DivLocalComponent
|
||||
import com.yandex.div.compose.dagger.DivViewComponent
|
||||
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 kotlin.collections.forEach
|
||||
import kotlin.collections.orEmpty
|
||||
|
||||
internal class DivViewContext(
|
||||
val rootLocalContext: DivLocalContext
|
||||
data: DivData,
|
||||
private val component: DivViewComponent
|
||||
) {
|
||||
/**
|
||||
* Stores [DivLocalContext]s for view elements.
|
||||
*/
|
||||
val localContextStorage = DivLocalContextStorage()
|
||||
|
||||
val rootLocalComponent: DivLocalComponent
|
||||
|
||||
val visibilityActionTracker: VisibilityActionTracker
|
||||
get() = component.visibilityActionTracker
|
||||
|
||||
init {
|
||||
val baseFunctionProvider = FunctionProviderDecorator(GeneratedBuiltinFunctionProvider)
|
||||
val functions = data.functions.orEmpty().toLocalFunctions()
|
||||
rootLocalComponent = createLocalComponent(
|
||||
variableController = DivVariableController(component.variableController),
|
||||
functionProvider = baseFunctionProvider + functions,
|
||||
triggers = data.variableTriggers.orEmpty(),
|
||||
variables = data.variables.orEmpty()
|
||||
)
|
||||
}
|
||||
|
||||
fun getLocalComponent(
|
||||
data: DivBase,
|
||||
parentComponent: DivLocalComponent
|
||||
): DivLocalComponent {
|
||||
component.localComponentStorage.get(data)?.let {
|
||||
return it
|
||||
}
|
||||
|
||||
val functions = data.functions.orEmpty().toLocalFunctions()
|
||||
val variables = data.variables.orEmpty()
|
||||
return createLocalComponent(
|
||||
variableController = if (variables.isEmpty()) {
|
||||
parentComponent.variableController
|
||||
} else {
|
||||
DivVariableController(parentComponent.variableController)
|
||||
},
|
||||
functionProvider = parentComponent.functionProvider + functions,
|
||||
triggers = data.variableTriggers.orEmpty(),
|
||||
variables = variables
|
||||
).also {
|
||||
component.localComponentStorage.put(data, it)
|
||||
}
|
||||
}
|
||||
|
||||
private fun createLocalComponent(
|
||||
variableController: DivVariableController,
|
||||
functionProvider: FunctionProviderDecorator,
|
||||
triggers: List<DivTrigger>,
|
||||
variables: List<DivVariable>,
|
||||
): DivLocalComponent {
|
||||
val localComponent = component.localComponent().build(
|
||||
functionProvider = functionProvider,
|
||||
variableController = variableController
|
||||
)
|
||||
|
||||
variables.forEach { variableData ->
|
||||
localComponent.variableAdapter.convert(variableData)?.let {
|
||||
variableController.declare(it)
|
||||
}
|
||||
}
|
||||
|
||||
triggers.forEach {
|
||||
localComponent.triggerStorage.add(it)
|
||||
}
|
||||
|
||||
return localComponent
|
||||
}
|
||||
}
|
||||
|
||||
internal val LocalDivViewContext = compositionLocalOf<DivViewContext> {
|
||||
|
||||
+6
-14
@@ -6,7 +6,6 @@ import com.yandex.div.compose.DivComposeConfiguration
|
||||
import com.yandex.div.compose.DivFontFamilyProvider
|
||||
import com.yandex.div.compose.DivReporter
|
||||
import com.yandex.div.compose.actions.DivActionHandler
|
||||
import com.yandex.div.compose.actions.VisibilityActionTracker
|
||||
import com.yandex.div.compose.context.DivViewContextStorage
|
||||
import com.yandex.div.compose.internal.DivDebugConfiguration
|
||||
import com.yandex.div.compose.internal.DivDebugFeatures
|
||||
@@ -31,25 +30,18 @@ internal interface DivContextComponent {
|
||||
val imageLoader: ImageLoader
|
||||
val reporter: DivReporter
|
||||
val viewContextStorage: DivViewContextStorage
|
||||
val visibilityActionTracker: VisibilityActionTracker
|
||||
|
||||
@get:Named(Names.HOST_VARIABLES)
|
||||
val variableController: DivVariableController
|
||||
|
||||
fun localComponent(): DivLocalComponent.Builder
|
||||
fun viewComponent(): DivViewComponent.Builder
|
||||
|
||||
@Component.Builder
|
||||
interface Builder {
|
||||
|
||||
@BindsInstance
|
||||
fun baseContext(baseContext: Context): Builder
|
||||
|
||||
@BindsInstance
|
||||
fun configuration(configuration: DivComposeConfiguration): Builder
|
||||
|
||||
@BindsInstance
|
||||
fun debugConfiguration(configuration: DivDebugConfiguration): Builder
|
||||
|
||||
fun build(): DivContextComponent
|
||||
fun build(
|
||||
@BindsInstance baseContext: Context,
|
||||
@BindsInstance configuration: DivComposeConfiguration,
|
||||
@BindsInstance debugConfiguration: DivDebugConfiguration
|
||||
): DivContextComponent
|
||||
}
|
||||
}
|
||||
|
||||
+39
-10
@@ -1,12 +1,21 @@
|
||||
package com.yandex.div.compose.dagger
|
||||
|
||||
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.context.LocalDivViewContext
|
||||
import com.yandex.div.compose.triggers.DivTriggerStorage
|
||||
import com.yandex.div.compose.triggers.observe
|
||||
import com.yandex.div.compose.variables.DivVariableAdapter
|
||||
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.div.json.expressions.ExpressionResolver
|
||||
import com.yandex.div2.DivBase
|
||||
import com.yandex.yatagan.BindsInstance
|
||||
import com.yandex.yatagan.Component
|
||||
import kotlin.collections.orEmpty
|
||||
|
||||
@DivLocalScope
|
||||
@Component(
|
||||
@@ -17,19 +26,39 @@ import com.yandex.yatagan.Component
|
||||
)
|
||||
internal interface DivLocalComponent {
|
||||
|
||||
val context: DivLocalContext
|
||||
val actionHandlingContext: DivActionHandlingContext
|
||||
val expressionResolver: ExpressionResolver
|
||||
val functionProvider: FunctionProviderDecorator
|
||||
val triggerStorage: DivTriggerStorage
|
||||
val variableAdapter: DivVariableAdapter
|
||||
val variableController: DivVariableController
|
||||
|
||||
@Component.Builder
|
||||
interface Builder {
|
||||
|
||||
@BindsInstance
|
||||
fun functionProvider(functionProvider: FunctionProviderDecorator): Builder
|
||||
|
||||
@BindsInstance
|
||||
fun variableController(variableController: DivVariableController): Builder
|
||||
|
||||
fun build(): DivLocalComponent
|
||||
fun build(
|
||||
@BindsInstance functionProvider: FunctionProviderDecorator,
|
||||
@BindsInstance variableController: DivVariableController
|
||||
): DivLocalComponent
|
||||
}
|
||||
}
|
||||
|
||||
internal val LocalComponent = compositionLocalOf<DivLocalComponent> {
|
||||
throw DivException("DivLocalComponent not provided")
|
||||
}
|
||||
|
||||
@Composable
|
||||
internal fun WithLocalComponent(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 localComponent = LocalDivViewContext.current.getLocalComponent(
|
||||
data,
|
||||
parentComponent = LocalComponent.current
|
||||
)
|
||||
localComponent.triggerStorage.observe()
|
||||
CompositionLocalProvider(LocalComponent provides localComponent, content)
|
||||
}
|
||||
|
||||
+25
@@ -0,0 +1,25 @@
|
||||
package com.yandex.div.compose.dagger
|
||||
|
||||
import com.yandex.div.compose.actions.VisibilityActionTracker
|
||||
import com.yandex.div.compose.context.DivLocalComponentStorage
|
||||
import com.yandex.div.core.expression.variables.DivVariableController
|
||||
import com.yandex.yatagan.Component
|
||||
import javax.inject.Named
|
||||
|
||||
@DivViewScope
|
||||
@Component(isRoot = false)
|
||||
internal interface DivViewComponent {
|
||||
|
||||
val localComponentStorage: DivLocalComponentStorage
|
||||
val visibilityActionTracker: VisibilityActionTracker
|
||||
|
||||
@get:Named(Names.HOST_VARIABLES)
|
||||
val variableController: DivVariableController
|
||||
|
||||
fun localComponent(): DivLocalComponent.Builder
|
||||
|
||||
@Component.Builder
|
||||
interface Builder {
|
||||
fun build(): DivViewComponent
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,7 @@
|
||||
package com.yandex.div.compose.dagger
|
||||
|
||||
import javax.inject.Scope
|
||||
|
||||
@Scope
|
||||
@Retention(AnnotationRetention.RUNTIME)
|
||||
internal annotation class DivViewScope
|
||||
+2
-2
@@ -26,7 +26,7 @@ class DivDebugFeatures @Inject internal constructor(
|
||||
* Returns [ExpressionResolver] associated with the given [DivData].
|
||||
*/
|
||||
fun getExpressionResolver(data: DivData): ExpressionResolver? {
|
||||
return viewContextStorage.get(data)?.rootLocalContext?.expressionResolver
|
||||
return viewContextStorage.get(data)?.rootLocalComponent?.expressionResolver
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -36,7 +36,7 @@ class DivDebugFeatures @Inject internal constructor(
|
||||
fun performAction(data: DivData, action: DivAction) {
|
||||
viewContextStorage.get(data)?.let {
|
||||
actionHandler.handle(
|
||||
context = it.rootLocalContext.actionHandlingContext,
|
||||
context = it.rootLocalComponent.actionHandlingContext,
|
||||
action = action,
|
||||
source = DivActionSource.EXTERNAL
|
||||
)
|
||||
|
||||
@@ -6,13 +6,17 @@ import coil3.ImageLoader
|
||||
import com.yandex.div.compose.DivContext
|
||||
import com.yandex.div.compose.DivFontFamilyProvider
|
||||
import com.yandex.div.compose.DivReporter
|
||||
import com.yandex.div.compose.context.LocalDivContext
|
||||
import com.yandex.div.compose.dagger.LocalComponent
|
||||
import com.yandex.div.json.expressions.ExpressionResolver
|
||||
|
||||
internal val divContext: DivContext
|
||||
@Composable
|
||||
get() = LocalContext.current as DivContext
|
||||
|
||||
internal val fontFamilyProvider: DivFontFamilyProvider
|
||||
@Composable
|
||||
get() = divContext.component.fontFamilyProvider
|
||||
|
||||
internal val imageLoader: ImageLoader
|
||||
@Composable
|
||||
get() = divContext.component.imageLoader
|
||||
@@ -21,10 +25,6 @@ internal val reporter: DivReporter
|
||||
@Composable
|
||||
get() = divContext.component.reporter
|
||||
|
||||
internal val fontFamilyProvider: DivFontFamilyProvider
|
||||
@Composable
|
||||
get() = divContext.component.fontFamilyProvider
|
||||
|
||||
internal val expressionResolver: ExpressionResolver
|
||||
@Composable
|
||||
get() = LocalDivContext.current.expressionResolver
|
||||
get() = LocalComponent.current.expressionResolver
|
||||
|
||||
+2
-2
@@ -1,6 +1,6 @@
|
||||
package com.yandex.div.compose.variables
|
||||
|
||||
import com.yandex.div.core.annotations.Mockable
|
||||
import com.yandex.div.compose.dagger.DivLocalScope
|
||||
import com.yandex.div.data.Variable
|
||||
import com.yandex.div.internal.data.PropertyVariableExecutor
|
||||
import com.yandex.div.internal.variables.toVariable
|
||||
@@ -9,7 +9,7 @@ import com.yandex.div.json.expressions.ExpressionResolver
|
||||
import com.yandex.div2.DivVariable
|
||||
import javax.inject.Inject
|
||||
|
||||
@Mockable
|
||||
@DivLocalScope
|
||||
internal class DivVariableAdapter @Inject constructor(
|
||||
private val expressionResolver: ExpressionResolver,
|
||||
private val parsingErrorLogger: ParsingErrorLogger
|
||||
|
||||
@@ -2,7 +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.dagger.WithLocalComponent
|
||||
import com.yandex.div.compose.views.container.DivContainerView
|
||||
import com.yandex.div.compose.views.gallery.DivGalleryView
|
||||
import com.yandex.div.compose.views.image.DivImageView
|
||||
@@ -21,9 +21,9 @@ internal fun DivBlockView(
|
||||
modifier: Modifier = Modifier,
|
||||
applyMargins: Boolean = true,
|
||||
) {
|
||||
WithLocalDivContext(data.value()) {
|
||||
WithLocalComponent(data.value()) {
|
||||
if (data.value().visibility.observedValue() == DivVisibility.GONE) {
|
||||
return@WithLocalDivContext
|
||||
return@WithLocalComponent
|
||||
}
|
||||
|
||||
val modifier = modifier.apply(data, applyMargins = applyMargins)
|
||||
|
||||
+2
-2
@@ -12,11 +12,11 @@ import androidx.compose.runtime.remember
|
||||
import androidx.compose.ui.Modifier
|
||||
import androidx.compose.ui.graphics.graphicsLayer
|
||||
import com.yandex.div.compose.actions.DivActionSource
|
||||
import com.yandex.div.compose.dagger.LocalComponent
|
||||
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.context.LocalDivContext
|
||||
import com.yandex.div.compose.utils.divContext
|
||||
import com.yandex.div2.Div
|
||||
import com.yandex.div2.DivAction
|
||||
@@ -32,7 +32,7 @@ internal fun Modifier.actions(data: Div): Modifier {
|
||||
}
|
||||
|
||||
val actionHandler = divContext.component.actionHandler
|
||||
val actionHandlingContext = LocalDivContext.current.actionHandlingContext
|
||||
val actionHandlingContext = LocalComponent.current.actionHandlingContext
|
||||
val onClick: () -> Unit = {
|
||||
actionHandler.handle(actionHandlingContext, actions, source = DivActionSource.TAP)
|
||||
}
|
||||
|
||||
+7
-7
@@ -3,8 +3,8 @@ package com.yandex.div.compose.views.modifiers
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.ui.Modifier
|
||||
import androidx.compose.ui.layout.onVisibilityChanged
|
||||
import com.yandex.div.compose.context.LocalDivContext
|
||||
import com.yandex.div.compose.utils.divContext
|
||||
import com.yandex.div.compose.context.LocalDivViewContext
|
||||
import com.yandex.div.compose.dagger.LocalComponent
|
||||
import com.yandex.div.compose.utils.observedIntValue
|
||||
import com.yandex.div.compose.utils.observedValue
|
||||
import com.yandex.div2.DivBase
|
||||
@@ -24,8 +24,8 @@ internal fun Modifier.visibilityActions(data: DivBase): Modifier {
|
||||
|
||||
@Composable
|
||||
private fun Modifier.visibilityActions(actions: List<DivVisibilityAction>): Modifier {
|
||||
val visibilityActionTracker = divContext.component.visibilityActionTracker
|
||||
val actionHandlingContext = LocalDivContext.current.actionHandlingContext
|
||||
val visibilityActionTracker = LocalDivViewContext.current.visibilityActionTracker
|
||||
val actionHandlingContext = LocalComponent.current.actionHandlingContext
|
||||
var modifier = this
|
||||
actions
|
||||
.filter { shouldRegisterVisibilityCallback(it) }
|
||||
@@ -46,8 +46,8 @@ private fun Modifier.visibilityActions(actions: List<DivVisibilityAction>): Modi
|
||||
|
||||
@Composable
|
||||
private fun Modifier.disappearActions(actions: List<DivDisappearAction>): Modifier {
|
||||
val visibilityActionTracker = divContext.component.visibilityActionTracker
|
||||
val actionHandlingContext = LocalDivContext.current.actionHandlingContext
|
||||
val visibilityActionTracker = LocalDivViewContext.current.visibilityActionTracker
|
||||
val actionHandlingContext = LocalComponent.current.actionHandlingContext
|
||||
var modifier = this
|
||||
actions
|
||||
.filter { shouldRegisterVisibilityCallback(it) }
|
||||
@@ -67,7 +67,7 @@ private fun Modifier.disappearActions(actions: List<DivDisappearAction>): Modifi
|
||||
|
||||
@Composable
|
||||
private fun shouldRegisterVisibilityCallback(action: DivSightAction): Boolean {
|
||||
return !divContext.component.visibilityActionTracker.isLimitReached(
|
||||
return !LocalDivViewContext.current.visibilityActionTracker.isLimitReached(
|
||||
action = action,
|
||||
limit = action.logLimit.observedIntValue()
|
||||
)
|
||||
|
||||
+2
-2
@@ -2,7 +2,7 @@ package com.yandex.div.compose.views.state
|
||||
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.runtime.remember
|
||||
import com.yandex.div.compose.context.LocalDivContext
|
||||
import com.yandex.div.compose.dagger.LocalComponent
|
||||
import com.yandex.div.compose.utils.expressionResolver
|
||||
import com.yandex.div.compose.utils.observedVariableValue
|
||||
import com.yandex.div.compose.utils.reporter
|
||||
@@ -16,7 +16,7 @@ internal fun DivState.observeActiveState(): DivState.State? {
|
||||
}
|
||||
|
||||
val activeStateId = stateVariable?.let {
|
||||
LocalDivContext.current.variableController.observedVariableValue(it)
|
||||
LocalComponent.current.variableController.observedVariableValue(it)
|
||||
}
|
||||
|
||||
val resolver = expressionResolver
|
||||
|
||||
+174
@@ -0,0 +1,174 @@
|
||||
package com.yandex.div.compose
|
||||
|
||||
import android.view.View
|
||||
import androidx.activity.ComponentActivity
|
||||
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.test.ext.junit.runners.AndroidJUnit4
|
||||
import com.yandex.div.compose.internal.DivDebugConfiguration
|
||||
import com.yandex.div.core.expression.variables.DivVariableController
|
||||
import com.yandex.div.data.Variable
|
||||
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.typedValue
|
||||
import com.yandex.div.test.data.visibilityAction
|
||||
import com.yandex.div.test.data.visibilityExpression
|
||||
import com.yandex.div2.DivActionTyped
|
||||
import com.yandex.div2.DivData
|
||||
import kotlinx.coroutines.ExperimentalCoroutinesApi
|
||||
import kotlinx.coroutines.test.TestScope
|
||||
import org.junit.Before
|
||||
import org.junit.Rule
|
||||
import org.junit.Test
|
||||
import org.junit.runner.RunWith
|
||||
import kotlin.math.min
|
||||
|
||||
@RunWith(AndroidJUnit4::class)
|
||||
class DivViewWithVisibilityActionsRecompositionTest {
|
||||
|
||||
@get:Rule
|
||||
val rule = createAndroidComposeRule<ComponentActivity>().apply {
|
||||
mainClock.autoAdvance = false
|
||||
}
|
||||
|
||||
private val activity: ComponentActivity
|
||||
get() = rule.activity
|
||||
|
||||
private val counter = Variable.IntegerVariable("counter", 0)
|
||||
private val visibility = Variable.StringVariable("visibility", "visible")
|
||||
|
||||
private val variableController = DivVariableController().apply {
|
||||
declare(counter, visibility)
|
||||
}
|
||||
|
||||
private val testScope = TestScope()
|
||||
|
||||
private lateinit var divContext: DivContext
|
||||
|
||||
@Before
|
||||
fun setUp() {
|
||||
divContext = DivContext(
|
||||
baseContext = activity,
|
||||
configuration = DivComposeConfiguration(
|
||||
reporter = TestReporter(),
|
||||
variableController = variableController
|
||||
),
|
||||
debugConfiguration = DivDebugConfiguration(
|
||||
coroutineScope = testScope
|
||||
)
|
||||
)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `visibility action limit does not reset when ComposeView is recreated with the same context`() {
|
||||
val data = data(
|
||||
content = text(
|
||||
id = "counter",
|
||||
text = expression("counter = @{counter}"),
|
||||
visibility = visibilityExpression("@{visibility}"),
|
||||
visibilityActions = listOf(
|
||||
visibilityAction(delayMs = 500, limit = 2, typed = incrementCounterAction())
|
||||
)
|
||||
)
|
||||
)
|
||||
|
||||
setContent(data)
|
||||
|
||||
rule.onNodeWithTag("counter").assertTextEquals("counter = 0")
|
||||
advanceTimeBy(500)
|
||||
rule.onNodeWithTag("counter").assertTextEquals("counter = 1")
|
||||
|
||||
activity.setContentView(View(activity))
|
||||
setContent(data)
|
||||
|
||||
rule.onNodeWithTag("counter").assertTextEquals("counter = 1")
|
||||
advanceTimeBy(500)
|
||||
|
||||
repeat(5) {
|
||||
rule.onNodeWithTag("counter").assertTextEquals("counter = 2")
|
||||
hideCounter()
|
||||
showCounter()
|
||||
advanceTimeBy(500)
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `visibility action limit resets when the view context was cleared`() {
|
||||
val data = data(
|
||||
content = text(
|
||||
id = "counter",
|
||||
text = expression("counter = @{counter}"),
|
||||
visibility = visibilityExpression("@{visibility}"),
|
||||
visibilityActions = listOf(
|
||||
visibilityAction(delayMs = 500, limit = 3, typed = incrementCounterAction())
|
||||
)
|
||||
)
|
||||
)
|
||||
|
||||
setContent(data)
|
||||
|
||||
rule.onNodeWithTag("counter").assertTextEquals("counter = 0")
|
||||
advanceTimeBy(500)
|
||||
rule.onNodeWithTag("counter").assertTextEquals("counter = 1")
|
||||
|
||||
activity.setContentView(View(activity))
|
||||
divContext.clearViewContext(data)
|
||||
counter.set(0)
|
||||
setContent(data)
|
||||
|
||||
repeat(5) {
|
||||
rule.onNodeWithTag("counter").assertTextEquals("counter = ${min(it, 3)}")
|
||||
hideCounter()
|
||||
showCounter()
|
||||
advanceTimeBy(500)
|
||||
}
|
||||
}
|
||||
|
||||
private fun setContent(data: DivData) {
|
||||
activity.setContentView(
|
||||
ComposeView(divContext).apply {
|
||||
setContent {
|
||||
DivView(data)
|
||||
}
|
||||
}
|
||||
)
|
||||
}
|
||||
|
||||
private fun showCounter() {
|
||||
withAutoAdvance {
|
||||
visibility.set("visible")
|
||||
rule.onNodeWithTag("counter").assertIsDisplayed()
|
||||
}
|
||||
}
|
||||
|
||||
private fun hideCounter() {
|
||||
withAutoAdvance {
|
||||
visibility.set("gone")
|
||||
rule.onNodeWithTag("counter").assertDoesNotExist()
|
||||
}
|
||||
}
|
||||
|
||||
@OptIn(ExperimentalCoroutinesApi::class)
|
||||
private fun advanceTimeBy(duration: Long) {
|
||||
testScope.testScheduler.advanceTimeBy(duration)
|
||||
testScope.testScheduler.runCurrent()
|
||||
rule.mainClock.advanceTimeBy(duration)
|
||||
rule.mainClock.advanceTimeByFrame()
|
||||
}
|
||||
|
||||
private fun withAutoAdvance(block: () -> Unit) {
|
||||
rule.mainClock.autoAdvance = true
|
||||
block()
|
||||
rule.mainClock.autoAdvance = false
|
||||
}
|
||||
}
|
||||
|
||||
private fun incrementCounterAction(): DivActionTyped {
|
||||
return setVariableAction("counter", typedValue(intExpression("@{counter + 1}")))
|
||||
}
|
||||
+10
-12
@@ -11,9 +11,9 @@ import androidx.test.ext.junit.runners.AndroidJUnit4
|
||||
import com.yandex.div.compose.DivComposeConfiguration
|
||||
import com.yandex.div.compose.DivContext
|
||||
import com.yandex.div.compose.TestReporter
|
||||
import com.yandex.div.compose.context.DivLocalContext
|
||||
import com.yandex.div.compose.context.LocalDivContext
|
||||
import com.yandex.div.compose.createExpressionResolver
|
||||
import com.yandex.div.compose.dagger.DivLocalComponent
|
||||
import com.yandex.div.compose.dagger.LocalComponent
|
||||
import com.yandex.div.core.expression.variables.DivVariableController
|
||||
import com.yandex.div.data.Variable
|
||||
import org.junit.Assert.assertEquals
|
||||
@@ -21,6 +21,7 @@ import org.junit.Assert.assertNull
|
||||
import org.junit.Rule
|
||||
import org.junit.Test
|
||||
import org.junit.runner.RunWith
|
||||
import org.mockito.kotlin.doReturn
|
||||
import org.mockito.kotlin.mock
|
||||
|
||||
@RunWith(AndroidJUnit4::class)
|
||||
@@ -29,19 +30,16 @@ class DivVariableControllerTest {
|
||||
@get:Rule
|
||||
val composeRule = createComposeRule()
|
||||
|
||||
private val variableController = DivVariableController()
|
||||
private val reporter = TestReporter()
|
||||
private val variableController = DivVariableController()
|
||||
|
||||
private val localContext = DivLocalContext(
|
||||
actionHandlingContext = mock(),
|
||||
expressionResolver = createExpressionResolver(
|
||||
private val localComponent = mock<DivLocalComponent> {
|
||||
on { expressionResolver } doReturn createExpressionResolver(
|
||||
reporter = reporter,
|
||||
variableController = variableController
|
||||
),
|
||||
functionProvider = mock(),
|
||||
triggerStorage = mock(),
|
||||
variableController = variableController
|
||||
)
|
||||
)
|
||||
on { variableController } doReturn variableController
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `returns current value of string variable`() {
|
||||
@@ -156,7 +154,7 @@ class DivVariableControllerTest {
|
||||
)
|
||||
CompositionLocalProvider(
|
||||
LocalContext provides divContext,
|
||||
LocalDivContext provides localContext,
|
||||
LocalComponent provides localComponent,
|
||||
) {
|
||||
content()
|
||||
}
|
||||
|
||||
+9
-11
@@ -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.context.DivLocalContext
|
||||
import com.yandex.div.compose.context.LocalDivContext
|
||||
import com.yandex.div.compose.dagger.DivLocalComponent
|
||||
import com.yandex.div.compose.dagger.LocalComponent
|
||||
import com.yandex.div.core.expression.variables.DivVariableController
|
||||
import com.yandex.div.data.Variable
|
||||
import com.yandex.div.json.expressions.Expression
|
||||
@@ -20,6 +20,7 @@ import org.junit.Assert.assertEquals
|
||||
import org.junit.Rule
|
||||
import org.junit.Test
|
||||
import org.junit.runner.RunWith
|
||||
import org.mockito.kotlin.doReturn
|
||||
import org.mockito.kotlin.mock
|
||||
|
||||
@RunWith(AndroidJUnit4::class)
|
||||
@@ -30,15 +31,12 @@ class ExpressionUtilsTest {
|
||||
|
||||
private val variableController = DivVariableController()
|
||||
|
||||
private val localContext = DivLocalContext(
|
||||
actionHandlingContext = mock(),
|
||||
expressionResolver = createExpressionResolver(
|
||||
private val localComponent = mock<DivLocalComponent> {
|
||||
on { expressionResolver } doReturn createExpressionResolver(
|
||||
variableController = variableController
|
||||
),
|
||||
functionProvider = mock(),
|
||||
triggerStorage = mock(),
|
||||
variableController = variableController
|
||||
)
|
||||
)
|
||||
on { variableController } doReturn variableController
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `observed value changes when variable changes`() {
|
||||
@@ -213,7 +211,7 @@ class ExpressionUtilsTest {
|
||||
|
||||
private fun setContent(content: @Composable () -> Unit) {
|
||||
composeRule.setContent {
|
||||
CompositionLocalProvider(LocalDivContext provides localContext) {
|
||||
CompositionLocalProvider(LocalComponent provides localComponent) {
|
||||
content()
|
||||
}
|
||||
}
|
||||
|
||||
+10
-12
@@ -11,9 +11,9 @@ import androidx.test.ext.junit.runners.AndroidJUnit4
|
||||
import com.yandex.div.compose.DivComposeConfiguration
|
||||
import com.yandex.div.compose.DivContext
|
||||
import com.yandex.div.compose.TestReporter
|
||||
import com.yandex.div.compose.context.DivLocalContext
|
||||
import com.yandex.div.compose.context.LocalDivContext
|
||||
import com.yandex.div.compose.createExpressionResolver
|
||||
import com.yandex.div.compose.dagger.DivLocalComponent
|
||||
import com.yandex.div.compose.dagger.LocalComponent
|
||||
import com.yandex.div.core.expression.variables.DivVariableController
|
||||
import com.yandex.div.data.DivModelInternalApi
|
||||
import com.yandex.div.data.Variable
|
||||
@@ -26,6 +26,7 @@ import org.junit.Assert.assertNotNull
|
||||
import org.junit.Rule
|
||||
import org.junit.Test
|
||||
import org.junit.runner.RunWith
|
||||
import org.mockito.kotlin.doReturn
|
||||
import org.mockito.kotlin.mock
|
||||
|
||||
@OptIn(DivModelInternalApi::class)
|
||||
@@ -35,19 +36,16 @@ class ObserveActiveStateTest {
|
||||
@get:Rule
|
||||
val composeRule = createComposeRule()
|
||||
|
||||
private val variableController = DivVariableController()
|
||||
private val reporter = TestReporter()
|
||||
private val variableController = DivVariableController()
|
||||
|
||||
private val localContext = DivLocalContext(
|
||||
actionHandlingContext = mock(),
|
||||
expressionResolver = createExpressionResolver(
|
||||
private val localComponent = mock<DivLocalComponent> {
|
||||
on { expressionResolver } doReturn createExpressionResolver(
|
||||
reporter = reporter,
|
||||
variableController = variableController
|
||||
),
|
||||
functionProvider = mock(),
|
||||
triggerStorage = mock(),
|
||||
variableController = variableController
|
||||
)
|
||||
)
|
||||
on { variableController } doReturn variableController
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `resolves state by stateIdVariable`() {
|
||||
@@ -200,7 +198,7 @@ class ObserveActiveStateTest {
|
||||
)
|
||||
CompositionLocalProvider(
|
||||
LocalContext provides divContext,
|
||||
LocalDivContext provides localContext,
|
||||
LocalComponent provides localComponent,
|
||||
) {
|
||||
content()
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user