Skip to content

Commit 9212b8c

Browse files
1. Added TagViewModifiers.kt modifier class
2. Added TagViewContainer custom layout 3. Added more tag when there is no space available. 4. Code refactoring.
1 parent 5cded94 commit 9212b8c

File tree

7 files changed

+405
-20
lines changed

7 files changed

+405
-20
lines changed
Lines changed: 0 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -1,15 +1,3 @@
11
<?xml version="1.0" encoding="utf-8"?>
22
<manifest xmlns:android="http://schemas.android.com/apk/res/android">
3-
4-
<application>
5-
<activity
6-
android:name="co.yml.coreui.core.test.TestActivity"
7-
android:exported="true">
8-
<intent-filter>
9-
<action android:name="android.intent.action.MAIN" />
10-
11-
<category android:name="android.intent.category.LAUNCHER" />
12-
</intent-filter>
13-
</activity>
14-
</application>
153
</manifest>
Lines changed: 1 addition & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,3 @@
11
<?xml version="1.0" encoding="utf-8"?>
22
<manifest xmlns:android="http://schemas.android.com/apk/res/android">
3-
<application>
4-
<activity android:name="androidx.activity.ComponentActivity" />
5-
</application>
6-
</manifest>
3+
</manifest>
Lines changed: 245 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,245 @@
1+
package co.yml.coreui.core.ui.ytag
2+
3+
import android.util.Log
4+
import androidx.compose.foundation.background
5+
import androidx.compose.foundation.border
6+
import androidx.compose.foundation.clickable
7+
import androidx.compose.foundation.layout.*
8+
import androidx.compose.foundation.shape.CircleShape
9+
import androidx.compose.foundation.shape.RoundedCornerShape
10+
import androidx.compose.runtime.Composable
11+
import androidx.compose.ui.Modifier
12+
import androidx.compose.ui.graphics.Color
13+
import androidx.compose.ui.layout.Layout
14+
import androidx.compose.ui.platform.testTag
15+
import androidx.compose.ui.tooling.preview.Preview
16+
import androidx.compose.ui.unit.Dp
17+
import androidx.compose.ui.unit.IntOffset
18+
import androidx.compose.ui.unit.dp
19+
import co.yml.coreui.core.ui.ytag.model.TagViewContainerModifiers
20+
import co.yml.coreui.core.ui.ytag.model.TagViewData
21+
import co.yml.coreui.core.ui.ytag.model.TagViewModifiers
22+
23+
/**
24+
* [TagViewContainer] compose method used for hosting multiple chips
25+
*
26+
* @param tagViewData Defines the list of tag view data
27+
* @param tagViewContainerModifiers collection of modifier elements that decorate or add behavior to TagView elements
28+
*/
29+
@Composable
30+
fun TagViewContainer(
31+
tagViewData: List<TagViewData>,
32+
tagViewContainerModifiers: TagViewContainerModifiers
33+
) {
34+
val updatedTagViewData = tagViewData.toMutableList()
35+
val moreTagModifier = TagViewModifiers.Builder()
36+
.shape(CircleShape)
37+
.backgroundColor(Color.Black)
38+
.textColor(Color.White)
39+
.build()
40+
41+
updatedTagViewData.add(TagViewData("More", moreTagModifier))
42+
43+
with(tagViewContainerModifiers) {
44+
val modifier = Modifier
45+
Box(
46+
modifier = modifier
47+
.width(width = width ?: Dp.Unspecified)
48+
.height(height = height)
49+
.defaultMinSize(minWidth = minWidth, minHeight = minHeight)
50+
.run {
51+
if (enableBorder) {
52+
border(
53+
width = borderWidth,
54+
color = borderColor,
55+
shape = shape
56+
)
57+
} else {
58+
background(color = backgroundColor, shape = shape)
59+
}
60+
}
61+
.clickable { }
62+
.testTag("tag_view_container")
63+
.padding(containerPaddingValues)
64+
.background(
65+
color = backgroundColor,
66+
shape = shape
67+
)
68+
) {
69+
TagViewContainerLayout(
70+
tagViewContainerModifiers = tagViewContainerModifiers,
71+
content = {
72+
updatedTagViewData.forEach {
73+
with(it) {
74+
TagView(
75+
text = text,
76+
leadingIcon = leadingIcon,
77+
trailingIcon = trailingIcon,
78+
enabled = enabled,
79+
tagViewModifiers = tagViewModifiers
80+
)
81+
}
82+
}
83+
})
84+
}
85+
}
86+
}
87+
88+
/**
89+
* [TagViewContainerLayout] used for creating a custom layout to hosting y tag
90+
* @param tagViewContainerModifiers collection of modifier elements that decorate or add behavior to tag view container
91+
* @param content content of the container [Tag views]
92+
*/
93+
@Composable
94+
fun TagViewContainerLayout(
95+
tagViewContainerModifiers: TagViewContainerModifiers,
96+
content: @Composable () -> Unit
97+
) {
98+
Layout(content = content) { measurables, constraints ->
99+
val looseConstraints = constraints.copy(
100+
minWidth = 0,
101+
minHeight = 0
102+
)
103+
104+
var currentRow = 0
105+
var currentOffset = IntOffset.Zero
106+
107+
//todo sree_ check whether the padding is correct
108+
109+
val placeAbles = measurables.map { measurable ->
110+
val placeAble = measurable.measure(looseConstraints)
111+
112+
//todo sree_ is horizontal and vertical space [surface] required
113+
if (currentOffset.x > 0f && currentOffset.x + placeAble.width + tagViewContainerModifiers.tagSpacingHorizontal.toPx()
114+
.toInt() > constraints.maxWidth
115+
) {
116+
Log.i(
117+
"check_measure",
118+
"c.XOffset: ${currentOffset.x} p.Width: ${placeAble.width} p.Height: ${placeAble.height} tagHSpace: ${
119+
tagViewContainerModifiers.tagSpacingHorizontal.toPx().toInt()
120+
} tagVSpace: ${
121+
tagViewContainerModifiers.tagSpacingVertical.toPx().toInt()
122+
} max w: ${constraints.maxWidth}"
123+
)
124+
currentRow += 1
125+
currentOffset =
126+
currentOffset.copy(
127+
x = 0,
128+
y = currentOffset.y + placeAble.height + tagViewContainerModifiers.tagSpacingVertical.toPx()
129+
.toInt()
130+
)
131+
}
132+
Log.i("check_measure", "common: ${currentOffset}")
133+
134+
placeAble to currentOffset.also {
135+
currentOffset = it.copy(
136+
x = it.x + placeAble.width + tagViewContainerModifiers.tagSpacingHorizontal.toPx()
137+
.toInt()
138+
)
139+
}
140+
}
141+
142+
layout(width = constraints.maxWidth,
143+
height = placeAbles.lastOrNull()?.run { first.height } ?: 0) {
144+
Log.i("check_placeAble", "size: ${placeAbles.size}")
145+
placeAbles.forEachIndexed { index, placeable ->
146+
val (placeable, offset) = placeable
147+
//check whether current item has enough space
148+
val currentItemHorizontalSpace =
149+
placeable.width + tagViewContainerModifiers.tagSpacingHorizontal.toPx()
150+
.toInt()
151+
val currentItemVerticalSpace =
152+
placeable.height + tagViewContainerModifiers.tagSpacingVertical.toPx()
153+
.toInt()
154+
155+
if (offset.x + currentItemHorizontalSpace < constraints.maxWidth && offset.y + currentItemVerticalSpace < constraints.maxHeight) {
156+
//current item has space
157+
Log.i("check_placeable", "index: $index current item has space")
158+
val nextItemIndex = index + 1
159+
if (nextItemIndex <= placeAbles.lastIndex) {
160+
val nextItemOffset = placeAbles[nextItemIndex].second
161+
162+
val nextItemHorizontalSpace =
163+
placeAbles[nextItemIndex].first.width + tagViewContainerModifiers.tagSpacingHorizontal.toPx()
164+
.toInt()
165+
val nextItemVerticalSpace =
166+
placeAbles[nextItemIndex].first.height + tagViewContainerModifiers.tagSpacingVertical.toPx()
167+
.toInt()
168+
169+
if (nextItemOffset.x + nextItemHorizontalSpace < constraints.maxWidth && nextItemOffset.y + nextItemVerticalSpace < constraints.maxHeight) {
170+
//next item has space
171+
Log.i(
172+
"check_placeable",
173+
"next index: $nextItemIndex nextItemOffset : $nextItemOffset next item has space and placed"
174+
)
175+
placeable.place(offset.x, offset.y)
176+
} else {
177+
//next item has no space
178+
//check space to accommodate more tag
179+
Log.i("check_placeable", "index: $index next item has no space")
180+
val moreTagPlaceAble = placeAbles.last()
181+
val moreTagHorizontalSpace =
182+
moreTagPlaceAble.first.width + tagViewContainerModifiers.tagSpacingHorizontal.toPx()
183+
.toInt()
184+
val moreTagVerticalSpace =
185+
moreTagPlaceAble.first.height + tagViewContainerModifiers.tagSpacingVertical.toPx()
186+
.toInt()
187+
188+
if (offset.x + moreTagHorizontalSpace < constraints.maxWidth && offset.y + moreTagVerticalSpace < constraints.maxHeight) {
189+
//place more tag
190+
Log.i("check_placeable", "index: $index more tag placed")
191+
moreTagPlaceAble.first.place(offset.x, offset.y)
192+
return@layout
193+
} else {
194+
Log.i("check_placeable", "index: $index more tag has no space")
195+
}
196+
}
197+
}
198+
} else {
199+
//current item has no space
200+
//check space to accommodate more tag
201+
Log.i("check_placeable", "index: $index current item has no space")
202+
val moreTagPlaceAble = placeAbles.last()
203+
val moreTagHorizontalSpace =
204+
moreTagPlaceAble.first.width + tagViewContainerModifiers.tagSpacingHorizontal.toPx()
205+
.toInt()
206+
val moreTagVerticalSpace =
207+
moreTagPlaceAble.first.height + tagViewContainerModifiers.tagSpacingVertical.toPx()
208+
.toInt()
209+
210+
if (offset.x + moreTagHorizontalSpace < constraints.maxWidth && offset.y + moreTagVerticalSpace < constraints.maxHeight) {
211+
//place more tag
212+
Log.i("check_placeable", "index: $index more tag placed")
213+
placeable.place(offset.x, offset.y)
214+
return@layout
215+
} else {
216+
Log.i("check_placeable", "index: $index more tag has no space")
217+
}
218+
}
219+
}
220+
}
221+
}
222+
}
223+
224+
@Preview(name = "Default Tag container")
225+
@Composable
226+
fun DefaultTagContainer() {
227+
val tagViewModifiers = TagViewModifiers.Builder()
228+
.build()
229+
val tagViewData = listOf(
230+
TagViewData(text = "Tag 1", tagViewModifiers = tagViewModifiers),
231+
TagViewData(text = "Tag 2", tagViewModifiers = tagViewModifiers),
232+
TagViewData(text = "Tag 3", tagViewModifiers = tagViewModifiers),
233+
TagViewData(text = "Tag 4", tagViewModifiers = tagViewModifiers)
234+
)
235+
236+
val tagViewContainerModifiers = TagViewContainerModifiers.Builder()
237+
.shape(RoundedCornerShape(10.dp))
238+
.backgroundColor(Color.Gray)
239+
.build()
240+
241+
TagViewContainer(
242+
tagViewData = tagViewData,
243+
tagViewContainerModifiers = tagViewContainerModifiers
244+
)
245+
}
Lines changed: 98 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,98 @@
1+
package co.yml.coreui.core.ui.ytag.model
2+
3+
import androidx.compose.foundation.layout.PaddingValues
4+
import androidx.compose.ui.graphics.Color
5+
import androidx.compose.ui.graphics.RectangleShape
6+
import androidx.compose.ui.graphics.Shape
7+
import androidx.compose.ui.unit.Dp
8+
import androidx.compose.ui.unit.dp
9+
10+
//todo sree_ is min width and min height required?
11+
/**
12+
* [TagViewContainerModifiers] Immutable collection of modifier elements that decorate or add behavior to TagView container.
13+
* @param minWidth define a default min width of TagViewContainer
14+
* @param minHeight define a default min height of TagViewContainer
15+
* @param width define width of TagViewContainer
16+
* @param height define height of TagViewContainer
17+
* @param enableBorder enable border for TagViewContainer
18+
* @param borderWidth define borderWidth of the TagViewContainer
19+
* @param borderColor define borderColor of the TagViewContainer
20+
* @param backgroundColor define backgroundColor of the TagViewContainer
21+
* @param shape defines the shape of the TagViewContainer
22+
* @param containerPaddingValues define padding for TagViewContainer
23+
* @param tagSpacingHorizontal horizontal padding between tag views
24+
* @param tagSpacingVertical vertical padding between tag views
25+
*
26+
*/
27+
data class TagViewContainerModifiers(
28+
val minWidth: Dp,
29+
val minHeight: Dp,
30+
val width: Dp?,
31+
val height: Dp,
32+
val enableBorder: Boolean,
33+
val borderWidth: Dp,
34+
val borderColor: Color,
35+
val backgroundColor: Color,
36+
val shape: Shape,
37+
val containerPaddingValues: PaddingValues,
38+
val tagSpacingHorizontal: Dp,
39+
val tagSpacingVertical: Dp
40+
) {
41+
//todo sree_ check min and max default size
42+
class Builder {
43+
private var minWidth: Dp = 150.dp
44+
private var minHeight: Dp = 150.dp
45+
private var width: Dp? = null
46+
private var height: Dp = minHeight
47+
private var enableBorder: Boolean = false
48+
private var borderWidth: Dp = 1.dp
49+
private var borderColor: Color = Color.Black
50+
private var backgroundColor: Color = Color.White
51+
private var shape: Shape = RectangleShape
52+
private var containerPaddingValues: PaddingValues = PaddingValues(horizontal = 4.dp, vertical = 4.dp)
53+
private var tagSpacingHorizontal: Dp = 8.dp
54+
private var tagSpacingVertical: Dp = 8.dp
55+
56+
57+
fun minWidth(minWidth: Dp) = apply { this.minWidth = minWidth }
58+
59+
fun minHeight(minHeight: Dp) = apply { this.minHeight = minHeight }
60+
61+
fun width(width: Dp) = apply { this.width = width }
62+
63+
fun height(height: Dp) = apply { this.height = height }
64+
65+
fun enableBorder(enableBorder: Boolean) = apply { this.enableBorder = enableBorder }
66+
67+
fun borderWidth(borderWidth: Dp) = apply { this.borderWidth = borderWidth }
68+
69+
fun borderColor(borderColor: Color) = apply { this.borderColor = borderColor }
70+
71+
fun backgroundColor(backgroundColor: Color) =
72+
apply { this.backgroundColor = backgroundColor }
73+
74+
fun shape(shape: Shape) = apply { this.shape = shape }
75+
76+
fun containerPaddingValues(paddingValues: PaddingValues) =
77+
apply { this.containerPaddingValues = paddingValues }
78+
79+
fun tagSpacingHorizontal(space: Dp) = apply { this.tagSpacingHorizontal = space }
80+
81+
fun tagSpacingVertical(space: Dp) = apply { this.tagSpacingVertical = space }
82+
83+
fun build() = TagViewContainerModifiers(
84+
minWidth,
85+
minHeight,
86+
width,
87+
height,
88+
enableBorder,
89+
borderWidth,
90+
borderColor,
91+
backgroundColor,
92+
shape,
93+
containerPaddingValues,
94+
tagSpacingHorizontal,
95+
tagSpacingVertical
96+
)
97+
}
98+
}
Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,20 @@
1+
package co.yml.coreui.core.ui.ytag.model
2+
3+
import androidx.compose.runtime.Composable
4+
5+
/**
6+
* [TagViewData] Used for holding the TagView data
7+
*
8+
* @param text text the text to be displayed
9+
* @param leadingIcon the optional leading icon to be displayed at the beginning of the TagView
10+
* @param trailingIcon the optional leading icon to be displayed at the end of the TagView
11+
* @param enabled controls the enabled state of the TagView
12+
* @param tagViewModifiers collection of modifier elements that decorate or add behavior to TagView elements
13+
*/
14+
data class TagViewData(
15+
val text: String,
16+
val tagViewModifiers: TagViewModifiers,
17+
val leadingIcon: @Composable ((enable: Boolean) -> Unit)? = null,
18+
val trailingIcon: @Composable ((enable: Boolean) -> Unit)? = null,
19+
val enabled: Boolean = true
20+
)

core/ui/src/main/java/co/yml/coreui/core/ui/ytag/model/TagViewModifiers.kt

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -19,7 +19,7 @@ import androidx.compose.ui.unit.sp
1919

2020

2121
/**
22-
* Immutable collection of modifier elements that decorate or add behavior to TagView elements.
22+
* [TagViewModifiers] Represents immutable collection of modifier elements that decorate or add behavior to TagView elements.
2323
* - If a parameter is explicitly set here then that parameter will always be used.
2424
* - If a parameter is not set then the corresponding default value will be used
2525
*

0 commit comments

Comments
 (0)