TP-23440 | added grid renderer (#8)

This commit is contained in:
Maila Rajanikanth
2023-04-12 17:25:13 +05:30
committed by GitHub Enterprise
parent 1a9969b504
commit 35cb81ecb1
13 changed files with 410 additions and 118 deletions

View File

@@ -18,13 +18,17 @@
android:label="@string/title_activity_main"
android:theme="@style/Theme.UiTron"
android:exported="true">
</activity>
<activity android:name=".MockActivity"
android:exported="true">
<intent-filter>
<action android:name="android.intent.action.MAIN" />
<category android:name="android.intent.category.LAUNCHER" />
</intent-filter>
</activity>
</application>
</manifest>

View File

@@ -0,0 +1,23 @@
package com.uitron.demo
import android.os.Bundle
import androidx.activity.ComponentActivity
import androidx.activity.compose.setContent
import com.google.gson.reflect.TypeToken
import com.navi.uitron.model.UiTronResponse
import com.navi.uitron.render.UiTronRenderer
import com.navi.uitron.viewmodel.UiTronViewModel
class MockActivity : ComponentActivity() {
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
val type = object : TypeToken<UiTronResponse>() {}.type
val response = mockApiResponse<UiTronResponse>(this, type, "shapeMock")
setContent {
UiTronRenderer(
response.data,
UiTronViewModel()
).Render(composeViews = response.parentComposeView!!)
}
}
}

View File

@@ -88,5 +88,118 @@
}
}
}
},
"gridMock": {
"parentComposeView": [
{
"property": {
"layoutId": "grid",
"viewType": "Grid",
"width": "MATCH_PARENT",
"height": "WRAP_CONTENT",
"spanCount": 3,
"itemSpacing": 8,
"backgroundColor": "#ABABAB",
"padding": {
"start": 8,
"end": 8,
"top": 8,
"bottom": 8
}
},
"childrenViews": [
{
"property": {
"layoutId": "item_1",
"viewType": "Text",
"width": "WRAP_CONTENT",
"height": "200",
"backgroundColor": "#639674"
}
},
{
"property": {
"layoutId": "item_2",
"viewType": "Text",
"width": "WRAP_CONTENT",
"height": "200",
"backgroundColor": "#639674"
}
},
{
"property": {
"layoutId": "item_3",
"viewType": "Text",
"width": "WRAP_CONTENT",
"height": "200",
"backgroundColor": "#639674"
}
},
{
"property": {
"layoutId": "item_4",
"viewType": "Text",
"width": "WRAP_CONTENT",
"height": "200",
"backgroundColor": "#639674"
}
},
{
"property": {
"layoutId": "item_5",
"viewType": "Text",
"width": "WRAP_CONTENT",
"height": "200",
"backgroundColor": "#639674"
}
}
]
}
],
"data": {
"item_1": {
"viewType": "Text",
"text": "Item 1 Item 1 Item 1 Item 1"
},
"item_2": {
"viewType": "Text",
"text": "Item 2 Item 2 Item 2 Item 2"
},
"item_3": {
"viewType": "Text",
"text": "Item 3 Item 3 Item 3 Item 3"
},
"item_4": {
"viewType": "Text",
"text": "Item 4 Item 4 Item 4 Item 4"
},
"item_5": {
"viewType": "Text",
"text": "Item 5 Item 5 Item 5 Item 5"
}
}
},
"shapeMock": {
"parentComposeView": [
{
"property": {
"viewType": "Spacer",
"height": "200",
"width": "200",
"shape": {
"shapeType": "RoundedCornerShape",
"radius": {
"topStart": 16,
"bottomEnd": 16
}
},
"backgroundColor": "#692738",
"padding": {
"start": 16,
"top": 16
}
}
}
]
}
}

View File

@@ -106,12 +106,15 @@ class ComposePropertyDeserializer : JsonDeserializer<BaseProperty> {
ComposeViewType.LazyRow.name -> {
context?.deserialize(jsonObject, LazyRowProperty::class.java)
}
ComposeViewType.Grid.name -> {
context?.deserialize(jsonObject, GridProperty::class.java)
ComposeViewType.LazyGrid.name -> {
context?.deserialize(jsonObject, LazyGridProperty::class.java)
}
ComposeViewType.SpannableText.name -> {
context?.deserialize(jsonObject, SpannableProperty::class.java)
}
ComposeViewType.Grid.name -> {
context?.deserialize(jsonObject, GridProperty::class.java)
}
else -> null
}
}

View File

@@ -121,8 +121,8 @@ class UiTronDataDeserializer : JsonDeserializer<UiTronData> {
ComposeViewType.Pager.name -> {
context?.deserialize(jsonObject, PagerData::class.java)
}
ComposeViewType.Grid.name -> {
context?.deserialize(jsonObject, GridData::class.java)
ComposeViewType.LazyGrid.name -> {
context?.deserialize(jsonObject, LazyGridData::class.java)
}
ComposeViewType.SpannableText.name -> {
context?.deserialize(jsonObject, SpannableTextData::class.java)

View File

@@ -1,6 +1,6 @@
package com.navi.uitron.model.data
data class GridData(
data class LazyGridData(
// (layoutId, span)
val childSpanCountMap: Map<String, Int>? = null
): UiTronData()

View File

@@ -1,39 +1,15 @@
package com.navi.uitron.model.ui
data class GridProperty(
var gridStateKey: String? = null,
var contentPadding: ComposePadding? = null,
var gridCell: GridCell? = null,
var orientation: String? = ORIENTATION_VERTICAL,
var reverseLayout: Boolean? = null,
var userScrollEnabled: Boolean? = null,
var horizontalArrangementData: ArrangementData? = null,
var verticalArrangementData: ArrangementData? = null,
) : BaseProperty() {
var spanCount: Int? = 3,
var itemSpacing: Int? = null
): BaseProperty() {
override fun copyNonNullFrom(property: BaseProperty?) {
super.copyNonNullFrom(property)
val gridProperty = property as? GridProperty?
gridProperty?.gridStateKey?.let { gridStateKey = it }
gridProperty?.contentPadding?.let { contentPadding = it }
gridProperty?.gridCell?.let { gridCell = it }
gridProperty?.orientation?.let { orientation = it }
gridProperty?.reverseLayout?.let { reverseLayout = it }
gridProperty?.userScrollEnabled?.let { userScrollEnabled = it }
gridProperty?.horizontalArrangementData?.let { horizontalArrangementData = it }
gridProperty?.verticalArrangementData?.let { verticalArrangementData = it }
}
data class GridCell(
var count: Int? = 3,
var minSize: Int? = 100,
var gridType: String? = GRID_TYPE_FIXED
)
companion object {
const val ORIENTATION_HORIZONTAL = "horizontal"
const val ORIENTATION_VERTICAL = "vertical"
const val GRID_TYPE_FIXED = "fixed"
const val GRID_TYPE_ADAPTIVE = "adaptive"
gridProperty?.spanCount?.let { spanCount = it }
gridProperty?.itemSpacing?.let { itemSpacing = it }
}
}

View File

@@ -0,0 +1,39 @@
package com.navi.uitron.model.ui
data class LazyGridProperty(
var gridStateKey: String? = null,
var contentPadding: ComposePadding? = null,
var gridCell: GridCell? = null,
var orientation: String? = ORIENTATION_VERTICAL,
var reverseLayout: Boolean? = null,
var userScrollEnabled: Boolean? = null,
var horizontalArrangementData: ArrangementData? = null,
var verticalArrangementData: ArrangementData? = null,
) : BaseProperty() {
override fun copyNonNullFrom(property: BaseProperty?) {
super.copyNonNullFrom(property)
val lazyGridProperty = property as? LazyGridProperty?
lazyGridProperty?.gridStateKey?.let { gridStateKey = it }
lazyGridProperty?.contentPadding?.let { contentPadding = it }
lazyGridProperty?.gridCell?.let { gridCell = it }
lazyGridProperty?.orientation?.let { orientation = it }
lazyGridProperty?.reverseLayout?.let { reverseLayout = it }
lazyGridProperty?.userScrollEnabled?.let { userScrollEnabled = it }
lazyGridProperty?.horizontalArrangementData?.let { horizontalArrangementData = it }
lazyGridProperty?.verticalArrangementData?.let { verticalArrangementData = it }
}
data class GridCell(
var count: Int? = 3,
var minSize: Int? = 100,
var gridType: String? = GRID_TYPE_FIXED
)
companion object {
const val ORIENTATION_HORIZONTAL = "horizontal"
const val ORIENTATION_VERTICAL = "vertical"
const val GRID_TYPE_FIXED = "fixed"
const val GRID_TYPE_ADAPTIVE = "adaptive"
}
}

View File

@@ -69,7 +69,16 @@ data class ComposePadding(
@Parcelize
data class UiTronShape(
val shapeType: String? = null,
val size: Int? = null
val size: Int? = null,
val radius: Radius? = null
) : Parcelable
@Parcelize
data class Radius(
var topStart: Int? = null,
var topEnd: Int? = null,
var bottomStart: Int? = null,
var bottomEnd: Int? = null
) : Parcelable
@Parcelize
@@ -133,8 +142,9 @@ enum class ComposeViewType {
PagerIndicator,
LazyColumn,
LazyRow,
Grid,
SpannableText
LazyGrid,
SpannableText,
Grid
}
enum class HorizontalArrangementType {

View File

@@ -1,32 +1,24 @@
package com.navi.uitron.render
import androidx.compose.foundation.layout.Arrangement
import androidx.compose.foundation.layout.Box
import androidx.compose.foundation.layout.Column
import androidx.compose.foundation.layout.Row
import androidx.compose.foundation.layout.fillMaxWidth
import androidx.compose.foundation.layout.padding
import androidx.compose.foundation.lazy.grid.GridCells
import androidx.compose.foundation.lazy.grid.GridItemSpan
import androidx.compose.foundation.lazy.grid.LazyGridScope
import androidx.compose.foundation.lazy.grid.LazyHorizontalGrid
import androidx.compose.foundation.lazy.grid.LazyVerticalGrid
import androidx.compose.foundation.lazy.grid.rememberLazyGridState
import androidx.compose.runtime.Composable
import androidx.compose.runtime.collectAsState
import androidx.compose.ui.Modifier
import androidx.compose.ui.unit.dp
import androidx.constraintlayout.compose.layoutId
import com.navi.uitron.model.data.GridData
import com.navi.uitron.model.data.UiTronData
import com.navi.uitron.model.ui.GridProperty
import com.navi.uitron.model.ui.UiTronView
import com.navi.uitron.viewmodel.UiTronViewModel
import customClickable
import getContentPaddingValues
import orFalse
import orTrue
import orVal
import setBackground
import setHeight
import setHorizontalArrangement
import setVerticalArrangement
import setWidth
class GridRenderer(
@@ -37,7 +29,6 @@ class GridRenderer(
@Composable
override fun Render(property: GridProperty, uiTronData: UiTronData?) {
val gridData = uiTronData as? GridData
if (property.isStateFul.orFalse()) {
val state = uiTronViewModel.handle.getStateFlow<String?>(
property.getPropertyId(),
@@ -45,7 +36,6 @@ class GridRenderer(
).collectAsState()
property.copyNonNullFrom(property.statesMap?.get(state.value))
}
val gridState = uiTronViewModel.stateHolder.getOrUpdateGridState(key = property.gridStateKey)
if (property.visible.orTrue()) {
val modifier = Modifier
.layoutId(property.layoutId.orEmpty())
@@ -65,68 +55,46 @@ class GridRenderer(
uiTronViewModel.handleActions(uiTronData?.onClick)
}, interaction = property.interaction, actions = uiTronData?.onClick?.actions
)
if (property.orientation == GridProperty.ORIENTATION_HORIZONTAL) {
LazyHorizontalGrid(
modifier = modifier,
state = gridState ?: rememberLazyGridState(),
contentPadding = getContentPaddingValues(property.contentPadding),
reverseLayout = property.reverseLayout.orFalse(),
verticalArrangement = Arrangement.setVerticalArrangement(arrangementData = property.verticalArrangementData),
horizontalArrangement = Arrangement.setHorizontalArrangement(arrangementData = property.horizontalArrangementData),
rows = if (property.gridCell?.gridType == GridProperty.GRID_TYPE_ADAPTIVE) {
GridCells.Adaptive(
(if ((property.gridCell?.minSize ?: 100) > 0) {
property.gridCell?.minSize ?: 100
} else {
100
}).dp
)
} else {
GridCells.Fixed(property.gridCell?.count ?: 3)
},
userScrollEnabled = property.userScrollEnabled ?: true,
content = {
getContent(gridData)
}
)
} else {
LazyVerticalGrid(
modifier = modifier,
state = gridState ?: rememberLazyGridState(),
contentPadding = getContentPaddingValues(property.contentPadding),
reverseLayout = property.reverseLayout.orFalse(),
verticalArrangement = Arrangement.setVerticalArrangement(arrangementData = property.verticalArrangementData),
horizontalArrangement = Arrangement.setHorizontalArrangement(arrangementData = property.horizontalArrangementData),
columns = if (property.gridCell?.gridType == GridProperty.GRID_TYPE_ADAPTIVE) {
GridCells.Adaptive(
(if ((property.gridCell?.minSize ?: 100) > 0) {
property.gridCell?.minSize ?: 100
} else {
100
}).dp
)
} else {
GridCells.Fixed(property.gridCell?.count ?: 3)
},
userScrollEnabled = property.userScrollEnabled ?: true,
content = {
getContent(gridData)
}
)
}
}
}
private fun LazyGridScope.getContent(gridData: GridData?) {
childrenComposeViews.forEach { uiTronView ->
item(
span = {
GridItemSpan(
currentLineSpan = gridData?.childSpanCountMap?.get(uiTronView.property?.layoutId).orVal(1)
)
Column(modifier = modifier) {
val itemCount = childrenComposeViews.size
val columns = property.spanCount ?: 3
var rows = (itemCount / (columns))
if (itemCount.mod(columns) > 0) {
rows += 1
}
for (rowId in 0 until rows) {
val firstIndex = rowId * columns
Row {
for (columnId in 0 until columns) {
val index = firstIndex + columnId
Box(
modifier = Modifier
.fillMaxWidth()
.weight(1f)
.padding(
start = if (columnId == 0) {
0.dp
} else {
(property.itemSpacing ?: 0).dp
},
top = if (rowId == 0) {
0.dp
} else {
(property.itemSpacing ?: 0).dp
}
)
) {
if (index < itemCount) {
uiTronRenderer.Render(composeViews = listOf(childrenComposeViews[index]))
}
}
}
}
}
) {
uiTronRenderer.Render(listOf(uiTronView))
}
}
}

View File

@@ -0,0 +1,134 @@
package com.navi.uitron.render
import androidx.compose.foundation.layout.Arrangement
import androidx.compose.foundation.layout.padding
import androidx.compose.foundation.lazy.grid.GridCells
import androidx.compose.foundation.lazy.grid.GridItemSpan
import androidx.compose.foundation.lazy.grid.LazyGridScope
import androidx.compose.foundation.lazy.grid.LazyHorizontalGrid
import androidx.compose.foundation.lazy.grid.LazyVerticalGrid
import androidx.compose.foundation.lazy.grid.rememberLazyGridState
import androidx.compose.runtime.Composable
import androidx.compose.runtime.collectAsState
import androidx.compose.ui.Modifier
import androidx.compose.ui.unit.dp
import androidx.constraintlayout.compose.layoutId
import com.navi.uitron.model.data.LazyGridData
import com.navi.uitron.model.data.UiTronData
import com.navi.uitron.model.ui.LazyGridProperty
import com.navi.uitron.model.ui.UiTronView
import com.navi.uitron.viewmodel.UiTronViewModel
import customClickable
import getContentPaddingValues
import orFalse
import orTrue
import orVal
import setBackground
import setHeight
import setHorizontalArrangement
import setVerticalArrangement
import setWidth
class LazyGridRenderer(
private val childrenComposeViews: List<UiTronView>,
private val uiTronRenderer: UiTronRenderer,
private val uiTronViewModel: UiTronViewModel
) : Renderer<LazyGridProperty> {
@Composable
override fun Render(property: LazyGridProperty, uiTronData: UiTronData?) {
val lazyGridData = uiTronData as? LazyGridData
if (property.isStateFul.orFalse()) {
val state = uiTronViewModel.handle.getStateFlow<String?>(
property.getPropertyId(),
null
).collectAsState()
property.copyNonNullFrom(property.statesMap?.get(state.value))
}
val gridState = uiTronViewModel.stateHolder.getOrUpdateGridState(key = property.gridStateKey)
if (property.visible.orTrue()) {
val modifier = Modifier
.layoutId(property.layoutId.orEmpty())
.setHeight(property.height)
.setWidth(property.width)
.setBackground(
property.backgroundColor, property.shape, property.backGroundBrushData
)
.padding(
start = property.padding?.start?.dp ?: 0.dp,
end = property.padding?.end?.dp ?: 0.dp,
top = property.padding?.top?.dp ?: 0.dp,
bottom = property.padding?.bottom?.dp ?: 0.dp
)
.customClickable(
{
uiTronViewModel.handleActions(uiTronData?.onClick)
}, interaction = property.interaction, actions = uiTronData?.onClick?.actions
)
if (property.orientation == LazyGridProperty.ORIENTATION_HORIZONTAL) {
LazyHorizontalGrid(
modifier = modifier,
state = gridState ?: rememberLazyGridState(),
contentPadding = getContentPaddingValues(property.contentPadding),
reverseLayout = property.reverseLayout.orFalse(),
verticalArrangement = Arrangement.setVerticalArrangement(arrangementData = property.verticalArrangementData),
horizontalArrangement = Arrangement.setHorizontalArrangement(arrangementData = property.horizontalArrangementData),
rows = if (property.gridCell?.gridType == LazyGridProperty.GRID_TYPE_ADAPTIVE) {
GridCells.Adaptive(
(if ((property.gridCell?.minSize ?: 100) > 0) {
property.gridCell?.minSize ?: 100
} else {
100
}).dp
)
} else {
GridCells.Fixed(property.gridCell?.count ?: 3)
},
userScrollEnabled = property.userScrollEnabled ?: true,
content = {
getContent(lazyGridData)
}
)
} else {
LazyVerticalGrid(
modifier = modifier,
state = gridState ?: rememberLazyGridState(),
contentPadding = getContentPaddingValues(property.contentPadding),
reverseLayout = property.reverseLayout.orFalse(),
verticalArrangement = Arrangement.setVerticalArrangement(arrangementData = property.verticalArrangementData),
horizontalArrangement = Arrangement.setHorizontalArrangement(arrangementData = property.horizontalArrangementData),
columns = if (property.gridCell?.gridType == LazyGridProperty.GRID_TYPE_ADAPTIVE) {
GridCells.Adaptive(
(if ((property.gridCell?.minSize ?: 100) > 0) {
property.gridCell?.minSize ?: 100
} else {
100
}).dp
)
} else {
GridCells.Fixed(property.gridCell?.count ?: 3)
},
userScrollEnabled = property.userScrollEnabled ?: true,
content = {
getContent(lazyGridData)
}
)
}
}
}
private fun LazyGridScope.getContent(lazyGridData: LazyGridData?) {
childrenComposeViews.forEach { uiTronView ->
item(
span = {
GridItemSpan(
currentLineSpan = lazyGridData?.childSpanCountMap?.get(uiTronView.property?.layoutId).orVal(1)
)
}
) {
uiTronRenderer.Render(listOf(uiTronView))
}
}
}
}

View File

@@ -298,9 +298,9 @@ class UiTronRenderer(
)
}
}
ComposeViewType.Grid.name -> {
(composeView.property as? GridProperty)?.let {
GridRenderer(
ComposeViewType.LazyGrid.name -> {
(composeView.property as? LazyGridProperty)?.let {
LazyGridRenderer(
childrenComposeViews = composeView.childrenViews.orEmpty(),
uiTronRenderer = this,
uiTronViewModel = uiTronViewModel
@@ -346,9 +346,20 @@ class UiTronRenderer(
)
}
}
ComposeViewType.Grid.name -> {
(composeView.property as? GridProperty)?.let {
GridRenderer(
childrenComposeViews = composeView.childrenViews.orEmpty(),
uiTronRenderer = this,
uiTronViewModel = uiTronViewModel
).Render(
property = it,
uiTronData = dataMap?.getOrElse(it.layoutId.orEmpty()) { null }
)
}
}
}
}
}
fun getData(layoutId: String?) = dataMap?.get(layoutId)
}

View File

@@ -24,19 +24,30 @@ object ShapeUtil {
fun getShape(shape: UiTronShape?): Shape {
return when (shape?.shapeType) {
ShapeType.RoundedCornerShape.name -> {
RoundedCornerShape(shape.size?.dp ?: 0.dp)
if (shape.radius != null) {
RoundedCornerShape(
shape.radius.topStart?.dp ?: 0.dp, shape.radius.topEnd?.dp ?: 0.dp,
shape.radius.bottomEnd?.dp ?: 0.dp, shape.radius.bottomStart?.dp ?: 0.dp
)
} else {
RoundedCornerShape(shape.size?.dp ?: 0.dp)
}
}
ShapeType.TopRoundedCornerShape.name -> {
val radius = (shape.size ?: 0).dp
RoundedCornerShape(radius, radius)
}
ShapeType.BottomRoundedCornerShape.name -> {
val radius = (shape.size ?: 0).dp
RoundedCornerShape(0.dp, 0.dp, radius, radius)
}
ShapeType.CircleShape.name -> {
CircleShape
}
else -> RectangleShape
}
}