mirror of
https://github.com/divkit/divkit.git
synced 2026-05-07 20:02:32 +00:00
Added Android Unit and UI tests for DivSelect
This commit is contained in:
+5
@@ -2,6 +2,7 @@ package com.yandex.div.core.expression.variables
|
||||
|
||||
import androidx.annotation.MainThread
|
||||
import com.yandex.div.core.Disposable
|
||||
import com.yandex.div.core.annotations.Mockable
|
||||
import com.yandex.div.core.dagger.DivScope
|
||||
import com.yandex.div.core.expression.ExpressionsRuntimeProvider
|
||||
import com.yandex.div.core.view2.Div2View
|
||||
@@ -10,6 +11,7 @@ import com.yandex.div.data.Variable
|
||||
import javax.inject.Inject
|
||||
|
||||
@DivScope
|
||||
@Mockable
|
||||
internal class TwoWayStringVariableBinder @Inject constructor(
|
||||
errorCollectors: ErrorCollectors,
|
||||
expressionsRuntimeProvider: ExpressionsRuntimeProvider
|
||||
@@ -21,6 +23,7 @@ internal class TwoWayStringVariableBinder @Inject constructor(
|
||||
}
|
||||
|
||||
@DivScope
|
||||
@Mockable
|
||||
internal class TwoWayIntegerVariableBinder @Inject constructor(
|
||||
errorCollectors: ErrorCollectors,
|
||||
expressionsRuntimeProvider: ExpressionsRuntimeProvider
|
||||
@@ -31,11 +34,13 @@ internal class TwoWayIntegerVariableBinder @Inject constructor(
|
||||
override fun Long.toStringValue() = toString()
|
||||
}
|
||||
|
||||
@Mockable
|
||||
internal abstract class TwoWayVariableBinder<T>(
|
||||
private val errorCollectors: ErrorCollectors,
|
||||
private val expressionsRuntimeProvider: ExpressionsRuntimeProvider
|
||||
) {
|
||||
|
||||
@Mockable
|
||||
interface Callbacks<T> {
|
||||
@MainThread
|
||||
fun onVariableChanged(value: T?)
|
||||
|
||||
@@ -87,7 +87,7 @@ internal class DivSelectBinder @Inject constructor(
|
||||
private fun DivSelectView.observeVariable(div: DivSelect, divView: Div2View, errorCollector: ErrorCollector) {
|
||||
val resolver = divView.expressionResolver
|
||||
|
||||
variableBinder.bindVariable(
|
||||
val subscription = variableBinder.bindVariable(
|
||||
divView,
|
||||
div.valueVariable,
|
||||
callbacks = object : TwoWayStringVariableBinder.Callbacks {
|
||||
@@ -116,6 +116,8 @@ internal class DivSelectBinder @Inject constructor(
|
||||
this@observeVariable.valueUpdater = valueUpdater
|
||||
}
|
||||
})
|
||||
|
||||
addSubscription(subscription)
|
||||
}
|
||||
|
||||
private fun DivSelectView.observeFontSize(div: DivSelect, resolver: ExpressionResolver) {
|
||||
|
||||
+112
@@ -0,0 +1,112 @@
|
||||
package com.yandex.div.core.view2.divs
|
||||
|
||||
import com.yandex.div.core.expression.variables.TwoWayStringVariableBinder
|
||||
import com.yandex.div.core.expression.variables.TwoWayVariableBinder
|
||||
import com.yandex.div.core.view2.DivTypefaceResolver
|
||||
import com.yandex.div.core.view2.divs.widgets.DivSelectView
|
||||
import com.yandex.div.core.view2.errors.ErrorCollectors
|
||||
import com.yandex.div.internal.widget.SelectView
|
||||
import com.yandex.div.json.expressions.ExpressionResolver
|
||||
import com.yandex.div2.Div
|
||||
import com.yandex.div2.DivSelect
|
||||
import org.junit.Assert
|
||||
import org.junit.Test
|
||||
import org.junit.runner.RunWith
|
||||
import org.mockito.kotlin.any
|
||||
import org.mockito.kotlin.anyOrNull
|
||||
import org.mockito.kotlin.argumentCaptor
|
||||
import org.mockito.kotlin.doReturn
|
||||
import org.mockito.kotlin.eq
|
||||
import org.mockito.kotlin.mock
|
||||
import org.mockito.kotlin.verify
|
||||
import org.mockito.kotlin.verifyNoMoreInteractions
|
||||
import org.robolectric.RobolectricTestRunner
|
||||
|
||||
@RunWith(RobolectricTestRunner::class)
|
||||
class DivSelectBinderTest : DivBinderTest() {
|
||||
private val divTypefaceResolver = mock<DivTypefaceResolver>()
|
||||
private val variableBinder = mock<TwoWayStringVariableBinder>() {
|
||||
on { bindVariable(any(), any(), any()) } doReturn mock()
|
||||
}
|
||||
private val errorCollectors = mock<ErrorCollectors> {
|
||||
on { getOrCreate(anyOrNull(), anyOrNull()) } doReturn mock()
|
||||
}
|
||||
private val captor = argumentCaptor<TwoWayVariableBinder.Callbacks<String>>()
|
||||
|
||||
private val underTest = DivSelectBinder(
|
||||
baseBinder = baseBinder,
|
||||
typefaceResolver = divTypefaceResolver,
|
||||
variableBinder = variableBinder,
|
||||
errorCollectors = errorCollectors
|
||||
)
|
||||
|
||||
private val div = UnitTestData(SELECT_DIR, "with_options.json").div as Div.Select
|
||||
private val divSelect = div.value
|
||||
private val view = (viewCreator.create(div, ExpressionResolver.EMPTY) as DivSelectView).apply {
|
||||
layoutParams = defaultLayoutParams()
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `bind value_variable`() {
|
||||
underTest.bindView(view, divSelect, divView)
|
||||
|
||||
verify(variableBinder).bindVariable(any(), eq(divSelect.valueVariable), any())
|
||||
verifyNoMoreInteractions(variableBinder)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `update text after variable changed`() {
|
||||
underTest.bindView(view, divSelect, divView)
|
||||
verify(variableBinder).bindVariable(any(), any(), captor.capture())
|
||||
|
||||
val (optionText, optionValue) = divSelect.options.evaluateLastOption()
|
||||
|
||||
view.assertTextApplied(optionText) {
|
||||
val callbacks = captor.allValues.single()
|
||||
callbacks.onVariableChanged(optionValue)
|
||||
}
|
||||
|
||||
verifyNoMoreInteractions(variableBinder)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `update text and variable after option selected`() {
|
||||
val viewStateChangeListener = mock<(String) -> Unit>()
|
||||
|
||||
underTest.bindView(view, divSelect, divView)
|
||||
verify(variableBinder).bindVariable(any(), any(), captor.capture())
|
||||
|
||||
val (optionText, optionValue) = divSelect.options.evaluateLastOption()
|
||||
|
||||
view.assertTextApplied(optionText) {
|
||||
val callbacks = captor.allValues.single()
|
||||
callbacks.setViewStateChangeListener(viewStateChangeListener)
|
||||
|
||||
view.onItemSelectedListener!!(divSelect.options.size - 1)
|
||||
}
|
||||
|
||||
verify(viewStateChangeListener).invoke(optionValue)
|
||||
verifyNoMoreInteractions(viewStateChangeListener)
|
||||
|
||||
verifyNoMoreInteractions(variableBinder)
|
||||
}
|
||||
|
||||
private fun List<DivSelect.Option>.evaluateLastOption(): Pair<String, String> {
|
||||
val option = last()
|
||||
|
||||
val optionValue = option.value.evaluate(divView.expressionResolver)
|
||||
val optionText = option.text?.evaluate(divView.expressionResolver) ?: optionValue
|
||||
|
||||
return optionText to optionValue
|
||||
}
|
||||
|
||||
private inline fun SelectView.assertTextApplied(expectedText: String, body: () -> Unit) {
|
||||
Assert.assertNotEquals(text, expectedText)
|
||||
body()
|
||||
Assert.assertEquals(text, expectedText)
|
||||
}
|
||||
|
||||
companion object {
|
||||
private const val SELECT_DIR = "div-select"
|
||||
}
|
||||
}
|
||||
+127
@@ -0,0 +1,127 @@
|
||||
package com.yandex.div
|
||||
|
||||
import androidx.test.espresso.Espresso.pressBack
|
||||
import androidx.test.rule.ActivityTestRule
|
||||
import com.yandex.div.rule.uiTestRule
|
||||
import com.yandex.div.steps.divSelect
|
||||
import com.yandex.divkit.demo.DummyActivity
|
||||
import com.yandex.test.util.assertNoPopupsAreDisplayed
|
||||
import com.yandex.test.util.assertPopupDisplayed
|
||||
import org.junit.Rule
|
||||
import org.junit.Test
|
||||
|
||||
|
||||
class DivSelectPopupVisibilityTest {
|
||||
|
||||
private val activityRule = ActivityTestRule(DummyActivity::class.java)
|
||||
|
||||
@get:Rule
|
||||
val rule = uiTestRule { activityRule }
|
||||
|
||||
@Test
|
||||
fun popupShown() {
|
||||
divSelect {
|
||||
activityRule.buildContainer()
|
||||
assertNoPopupsAreDisplayed()
|
||||
|
||||
clickOnSelect()
|
||||
assertPopupDisplayed()
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
fun popupDismissedOnItemTap() {
|
||||
divSelect {
|
||||
activityRule.buildContainer()
|
||||
assertNoPopupsAreDisplayed()
|
||||
|
||||
clickOnSelect()
|
||||
assertPopupDisplayed()
|
||||
|
||||
selectFirstOption()
|
||||
assertNoPopupsAreDisplayed()
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
fun popupDismissedOnPressBack() {
|
||||
// Press back should work the same as click outside of popup
|
||||
divSelect {
|
||||
activityRule.buildContainer()
|
||||
assertNoPopupsAreDisplayed()
|
||||
|
||||
clickOnSelect()
|
||||
assertPopupDisplayed()
|
||||
|
||||
pressBack()
|
||||
assertNoPopupsAreDisplayed()
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
fun popupDismissedOnVisibilityInvisible() {
|
||||
divSelect {
|
||||
activityRule.buildContainer()
|
||||
assertNoPopupsAreDisplayed()
|
||||
|
||||
openSelectWithDelayedAction("visibility_invisible")
|
||||
|
||||
assertNoPopupsAreDisplayed()
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
fun popupDismissedOnVisibilityGone() {
|
||||
divSelect {
|
||||
activityRule.buildContainer()
|
||||
assertNoPopupsAreDisplayed()
|
||||
|
||||
openSelectWithDelayedAction("visibility_gone")
|
||||
|
||||
assertNoPopupsAreDisplayed()
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
fun popupAlignedWithAnchor() {
|
||||
divSelect {
|
||||
activityRule.buildContainer()
|
||||
assertNoPopupsAreDisplayed()
|
||||
|
||||
val select = stealSelect()
|
||||
|
||||
clickOnSelect()
|
||||
assertPopupDisplayed()
|
||||
|
||||
assert { popupAlignedWithAnchor(select) }
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
fun popupAlignedWithAnchorAfterWidthChange() {
|
||||
divSelect {
|
||||
activityRule.buildContainer()
|
||||
assertNoPopupsAreDisplayed()
|
||||
|
||||
val select = stealSelect()
|
||||
|
||||
openSelectWithDelayedAction("width_change")
|
||||
|
||||
assert { popupAlignedWithAnchor(select) }
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
fun popupAlignedWithAnchorAfterPositionChange() {
|
||||
divSelect {
|
||||
activityRule.buildContainer()
|
||||
assertNoPopupsAreDisplayed()
|
||||
|
||||
val select = stealSelect()
|
||||
|
||||
openSelectWithDelayedAction("position_change")
|
||||
|
||||
assert { popupAlignedWithAnchor(select) }
|
||||
}
|
||||
}
|
||||
}
|
||||
+100
@@ -0,0 +1,100 @@
|
||||
package com.yandex.div.steps
|
||||
|
||||
import android.view.View
|
||||
import android.view.ViewGroup.LayoutParams.MATCH_PARENT
|
||||
import android.widget.TextView
|
||||
import androidx.test.espresso.Espresso.onView
|
||||
import androidx.test.espresso.IdlingResource
|
||||
import androidx.test.espresso.ViewInteraction
|
||||
import androidx.test.espresso.matcher.ViewMatchers
|
||||
import androidx.test.espresso.matcher.ViewMatchers.withText
|
||||
import androidx.test.rule.ActivityTestRule
|
||||
import com.yandex.div.view.click
|
||||
import com.yandex.div.view.tap
|
||||
import com.yandex.divkit.demo.DummyActivity
|
||||
import com.yandex.test.idling.SimpleIdlingResource
|
||||
import com.yandex.test.idling.waitForIdlingResource
|
||||
import com.yandex.test.util.Report.step
|
||||
import com.yandex.test.util.StepsDsl
|
||||
import com.yandex.test.util.assertPopupDisplayed
|
||||
import org.hamcrest.Matchers
|
||||
|
||||
private const val TIMER_FINISHED_TEXT = "DONE"
|
||||
|
||||
internal fun divSelect(f: DivSelectSteps.() -> Unit) = f(DivSelectSteps())
|
||||
|
||||
@StepsDsl
|
||||
class DivSelectSteps : DivTestAssetSteps() {
|
||||
private val selectInteraction = onView(ViewMatchers.withHint("Select country"))
|
||||
private val firstItemInteraction = onView(withText("United Kingdom"))
|
||||
private val timerInteraction = onView(Matchers.anyOf(withText("IN PROGRESS")))
|
||||
|
||||
fun ActivityTestRule<DummyActivity>.buildContainer() {
|
||||
testAsset = "ui_test_data/select/select_with_default.json"
|
||||
buildContainer(MATCH_PARENT, MATCH_PARENT)
|
||||
}
|
||||
|
||||
fun clickOnSelect(): Unit = step("Click on select") {
|
||||
selectInteraction.click()
|
||||
}
|
||||
|
||||
fun selectFirstOption(): Unit = step("Select first option") {
|
||||
firstItemInteraction.tap()
|
||||
}
|
||||
|
||||
|
||||
fun openSelectWithDelayedAction(action: String) = step("Open select with delayed action") {
|
||||
val timer = prepareTimerIdlingResource()
|
||||
invokeDelayedAction(action)
|
||||
|
||||
clickOnSelect()
|
||||
assertPopupDisplayed()
|
||||
|
||||
waitForIdlingResource(timer)
|
||||
}
|
||||
|
||||
fun stealSelect(): View = step("Steal select view") {
|
||||
selectInteraction.stealView()
|
||||
}
|
||||
|
||||
private fun invokeDelayedAction(action: String): Unit =
|
||||
step("Invoke action $action with delay") {
|
||||
onView(withText(action)).click()
|
||||
}
|
||||
|
||||
private fun prepareTimerIdlingResource(): IdlingResource =
|
||||
step("Prepare timer idling resource") {
|
||||
timerInteraction.stealView().asIdlingResource {
|
||||
(this as TextView).text == TIMER_FINISHED_TEXT
|
||||
}
|
||||
}
|
||||
|
||||
private inline fun View.asIdlingResource(crossinline checkIdle: View.() -> Boolean) =
|
||||
object : SimpleIdlingResource() {
|
||||
override fun checkIdle(): Boolean = checkIdle(this@asIdlingResource)
|
||||
}
|
||||
|
||||
private fun ViewInteraction.stealView(): View {
|
||||
var stolenView: View? = null
|
||||
check { view, _ -> stolenView = view }
|
||||
return stolenView!!
|
||||
}
|
||||
|
||||
fun assert(f: DivSelectAssertions.() -> Unit) = f(DivSelectAssertions())
|
||||
}
|
||||
|
||||
@StepsDsl
|
||||
class DivSelectAssertions {
|
||||
private val popupInteraction = onView(ViewMatchers.withChild(withText("United Kingdom")))
|
||||
|
||||
fun popupAlignedWithAnchor(anchorView: View) {
|
||||
popupInteraction.check { popup, _ ->
|
||||
val (left, top) = IntArray(2).apply { popup.getLocationOnScreen(this) }
|
||||
val (anchorLeft, anchorTop) = IntArray(2).apply { anchorView.getLocationOnScreen(this) }
|
||||
|
||||
assert(left == anchorLeft)
|
||||
assert(top == anchorTop)
|
||||
assert(popup.width == anchorView.width)
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -12,6 +12,12 @@ fun assertNoPopupsAreDisplayed() {
|
||||
activeRoots.none { RootMatchers.isPlatformPopup().matches(it) })
|
||||
}
|
||||
|
||||
fun assertPopupDisplayed() {
|
||||
Assert.assertNotNull("Exactly one popup view must exist",
|
||||
activeRoots.singleOrNull { RootMatchers.isPlatformPopup().matches(it) }
|
||||
)
|
||||
}
|
||||
|
||||
internal val activeRoots: List<Root>
|
||||
get() = performOnMain {
|
||||
Reflection.staticField("BASE")
|
||||
|
||||
BIN
Binary file not shown.
|
After Width: | Height: | Size: 18 KiB |
BIN
Binary file not shown.
|
After Width: | Height: | Size: 5.6 KiB |
@@ -0,0 +1,240 @@
|
||||
{
|
||||
"templates": {
|
||||
"button": {
|
||||
"type": "text",
|
||||
"paddings": {
|
||||
"bottom": 16,
|
||||
"top": 16,
|
||||
"left": 16,
|
||||
"right": 16
|
||||
}
|
||||
}
|
||||
},
|
||||
"card": {
|
||||
"log_id": "select",
|
||||
"variables": [
|
||||
{
|
||||
"name": "value_variable",
|
||||
"type": "string",
|
||||
"value": ""
|
||||
},
|
||||
{
|
||||
"name": "visibility",
|
||||
"type": "string",
|
||||
"value": "visible"
|
||||
},
|
||||
{
|
||||
"name": "timer_finished",
|
||||
"type": "string",
|
||||
"value": "IN PROGRESS"
|
||||
},
|
||||
{
|
||||
"name": "width",
|
||||
"type": "integer",
|
||||
"value": 200
|
||||
},
|
||||
{
|
||||
"name": "margin",
|
||||
"type": "integer",
|
||||
"value": 16
|
||||
}
|
||||
],
|
||||
"timers": [
|
||||
{
|
||||
"id": "visibility_invisible",
|
||||
"duration": 1000,
|
||||
"end_actions": [
|
||||
{
|
||||
"log_id": "end",
|
||||
"url": "div-action://set_variable?name=visibility&value=invisible"
|
||||
},
|
||||
{
|
||||
"log_id": "end",
|
||||
"url": "div-action://set_variable?name=timer_finished&value=DONE"
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"id": "visibility_gone",
|
||||
"duration": 1000,
|
||||
"end_actions": [
|
||||
{
|
||||
"log_id": "end",
|
||||
"url": "div-action://set_variable?name=visibility&value=gone"
|
||||
},
|
||||
{
|
||||
"log_id": "end",
|
||||
"url": "div-action://set_variable?name=timer_finished&value=DONE"
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"id": "width_change",
|
||||
"duration": 1000,
|
||||
"end_actions": [
|
||||
{
|
||||
"log_id": "end",
|
||||
"url": "div-action://set_variable?name=width&value=150"
|
||||
},
|
||||
{
|
||||
"log_id": "end",
|
||||
"url": "div-action://set_variable?name=timer_finished&value=DONE"
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"id": "position_change",
|
||||
"duration": 1000,
|
||||
"end_actions": [
|
||||
{
|
||||
"log_id": "end",
|
||||
"url": "div-action://set_variable?name=margin&value=64"
|
||||
},
|
||||
{
|
||||
"log_id": "end",
|
||||
"url": "div-action://set_variable?name=timer_finished&value=DONE"
|
||||
}
|
||||
]
|
||||
}
|
||||
],
|
||||
"states": [
|
||||
{
|
||||
"state_id": 0,
|
||||
"div": {
|
||||
"type": "container",
|
||||
"width": {
|
||||
"type": "match_parent"
|
||||
},
|
||||
"height": {
|
||||
"type": "wrap_content"
|
||||
},
|
||||
"items": [
|
||||
{
|
||||
"type": "text",
|
||||
"text": "@{timer_finished}"
|
||||
},
|
||||
{
|
||||
"type": "select",
|
||||
"width": {
|
||||
"type": "fixed",
|
||||
"value": "@{width}"
|
||||
},
|
||||
"height": {
|
||||
"type": "wrap_content"
|
||||
},
|
||||
"margins": {
|
||||
"left": "@{margin}",
|
||||
"top": "@{margin}"
|
||||
},
|
||||
"paddings": {
|
||||
"left": 16,
|
||||
"top": 10,
|
||||
"right": 16,
|
||||
"bottom": 10
|
||||
},
|
||||
"alpha": 1,
|
||||
"alignment_horizontal": "center",
|
||||
"alignment_vertical": "center",
|
||||
"background": [
|
||||
{
|
||||
"type": "solid",
|
||||
"color": "#0e000000"
|
||||
}
|
||||
],
|
||||
"border": {
|
||||
"corner_radius": 8
|
||||
},
|
||||
"font_size": 16,
|
||||
"font_weight": "medium",
|
||||
"text_color": "#000000",
|
||||
"value_variable": "value_variable",
|
||||
"hint_text": "Select country",
|
||||
"hint_color": "#888888",
|
||||
"line_height": 22,
|
||||
"visibility": "@{visibility}",
|
||||
"options": [
|
||||
{
|
||||
"value": ""
|
||||
},
|
||||
{
|
||||
"value": "ru",
|
||||
"text": "Russia"
|
||||
},
|
||||
{
|
||||
"value": "uk",
|
||||
"text": "United Kingdom"
|
||||
},
|
||||
{
|
||||
"value": "kz"
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"type": "text",
|
||||
"width": {
|
||||
"type": "match_parent"
|
||||
},
|
||||
"height": {
|
||||
"type": "wrap_content"
|
||||
},
|
||||
"paddings": {
|
||||
"left": 18,
|
||||
"right": 16,
|
||||
"bottom": 16
|
||||
},
|
||||
"font_size": 16,
|
||||
"text_color": "#000000",
|
||||
"text": "Text: @{value_variable}"
|
||||
},
|
||||
{
|
||||
"type": "text",
|
||||
"text": "Timers",
|
||||
"margins": {
|
||||
"left": 16,
|
||||
"top": 8
|
||||
},
|
||||
"font_size": 16
|
||||
},
|
||||
{
|
||||
"type": "container",
|
||||
"items": [
|
||||
{
|
||||
"type": "button",
|
||||
"text": "visibility_invisible",
|
||||
"action": {
|
||||
"log_id": "visibility_invisible",
|
||||
"url": "div-action://timer?id=visibility_invisible&action=start"
|
||||
}
|
||||
},
|
||||
{
|
||||
"type": "button",
|
||||
"text": "visibility_gone",
|
||||
"action": {
|
||||
"log_id": "visibility_gone",
|
||||
"url": "div-action://timer?id=visibility_gone&action=start"
|
||||
}
|
||||
},
|
||||
{
|
||||
"type": "button",
|
||||
"text": "width_change",
|
||||
"action": {
|
||||
"log_id": "width_change",
|
||||
"url": "div-action://timer?id=width_change&action=start"
|
||||
}
|
||||
},
|
||||
{
|
||||
"type": "button",
|
||||
"text": "position_change",
|
||||
"action": {
|
||||
"log_id": "position_change",
|
||||
"url": "div-action://timer?id=position_change&action=start"
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,53 @@
|
||||
{
|
||||
"type": "select",
|
||||
"width": {
|
||||
"type": "match_parent"
|
||||
},
|
||||
"height": {
|
||||
"type": "wrap_content"
|
||||
},
|
||||
"margins": {
|
||||
"left": 16,
|
||||
"top": 20,
|
||||
"right": 16,
|
||||
"bottom": 16
|
||||
},
|
||||
"paddings": {
|
||||
"left": 16,
|
||||
"top": 10,
|
||||
"right": 16,
|
||||
"bottom": 10
|
||||
},
|
||||
"alpha": 1.0,
|
||||
"alignment_horizontal": "center",
|
||||
"alignment_vertical": "center",
|
||||
"background": [
|
||||
{
|
||||
"type": "solid",
|
||||
"color": "#0e000000"
|
||||
}
|
||||
],
|
||||
"border": {
|
||||
"corner_radius": 8
|
||||
},
|
||||
"font_size": 16,
|
||||
"font_weight": "medium",
|
||||
"text_color": "#000000",
|
||||
"value_variable": "value_variable",
|
||||
"hint_text": "Select country",
|
||||
"hint_color": "#888888",
|
||||
"line_height": 22,
|
||||
"options": [
|
||||
{
|
||||
"value": "ru",
|
||||
"text": "Russia"
|
||||
},
|
||||
{
|
||||
"value": "uk",
|
||||
"text": "United Kingdom"
|
||||
},
|
||||
{
|
||||
"value": "kz"
|
||||
}
|
||||
]
|
||||
}
|
||||
Reference in New Issue
Block a user