1+ package com.smarttoolfactory.composecolorsextended
2+
3+ import android.widget.Toast
4+ import androidx.compose.foundation.background
5+ import androidx.compose.foundation.clickable
6+ import androidx.compose.foundation.layout.*
7+ import androidx.compose.foundation.lazy.grid.GridCells
8+ import androidx.compose.foundation.lazy.grid.LazyVerticalGrid
9+ import androidx.compose.foundation.lazy.grid.itemsIndexed
10+ import androidx.compose.foundation.shape.RoundedCornerShape
11+ import androidx.compose.material.Icon
12+ import androidx.compose.material.IconButton
13+ import androidx.compose.material.Text
14+ import androidx.compose.material.icons.Icons
15+ import androidx.compose.material.icons.filled.Check
16+ import androidx.compose.runtime.*
17+ import androidx.compose.ui.Alignment
18+ import androidx.compose.ui.Modifier
19+ import androidx.compose.ui.draw.clip
20+ import androidx.compose.ui.graphics.Color
21+ import androidx.compose.ui.platform.LocalClipboardManager
22+ import androidx.compose.ui.platform.LocalContext
23+ import androidx.compose.ui.res.painterResource
24+ import androidx.compose.ui.text.AnnotatedString
25+ import androidx.compose.ui.text.font.FontWeight
26+ import androidx.compose.ui.text.style.TextAlign
27+ import androidx.compose.ui.unit.dp
28+ import androidx.compose.ui.unit.sp
29+ import com.smarttoolfactory.extendedcolors.ColorSwatch
30+ import com.smarttoolfactory.extendedcolors.MaterialColor
31+ import com.smarttoolfactory.extendedcolors.parser.rememberColorParser
32+ import com.smarttoolfactory.extendedcolors.util.colorToHSL
33+ import com.smarttoolfactory.extendedcolors.util.colorToHex
34+ import com.smarttoolfactory.extendedcolors.util.getColorTonesList
35+ import com.smarttoolfactory.extendedcolors.util.material3ToneRange
36+ import kotlinx.coroutines.Dispatchers
37+ import kotlinx.coroutines.flow.distinctUntilChanged
38+ import kotlinx.coroutines.flow.flowOn
39+ import kotlinx.coroutines.flow.mapLatest
40+
41+ @Composable
42+ fun M3ColorPicker (onColorChange : (Color ) -> Unit ) {
43+
44+ val clipboardManager = LocalClipboardManager .current
45+ val context = LocalContext .current
46+
47+ Column (
48+ modifier = Modifier
49+ .fillMaxSize()
50+ .background(Color .Black .copy(alpha = .8f )),
51+ horizontalAlignment = Alignment .CenterHorizontally
52+ ) {
53+
54+ val colorNameParser = rememberColorParser()
55+
56+ var colorSwatchIndex by remember { mutableStateOf(0 ) }
57+ var color by remember { mutableStateOf(MaterialColor .Red500 ) }
58+ var md3Tones by remember { mutableStateOf(getColorTonesList(color)) }
59+
60+ val colorSelectionIndex =
61+ remember { ColorSelectionIndex (mainSelection = 0 , subSelection = 0 ) }
62+
63+ val colorSwatch: LinkedHashMap <Int , Color > =
64+ remember(colorSwatchIndex) { ColorSwatch .primaryColorSwatches[colorSwatchIndex] }
65+
66+ val keys: MutableList <Int > = colorSwatch.keys.toMutableList()
67+ val colors: MutableList <Color > = colorSwatch.values.toMutableList()
68+
69+ var colorName by remember { mutableStateOf(" " ) }
70+
71+ LaunchedEffect (key1 = colorNameParser) {
72+
73+ snapshotFlow { color }
74+ .distinctUntilChanged()
75+ .mapLatest { color: Color ->
76+ colorNameParser.parseColorName(color)
77+ }
78+ .flowOn(Dispatchers .Default )
79+ .collect { name: String ->
80+ colorName = name
81+ }
82+ }
83+
84+ LazyVerticalGrid (
85+ columns = GridCells .Fixed (6 ),
86+ contentPadding = PaddingValues (8 .dp),
87+ verticalArrangement = Arrangement .spacedBy(4 .dp),
88+ horizontalArrangement = Arrangement .spacedBy(4 .dp),
89+ ) {
90+ itemsIndexed(ColorSwatch .primaryHeaderColors) { index: Int , item: Color ->
91+
92+ ColorDisplayWithIcon (
93+ modifier = Modifier
94+ .clip(RoundedCornerShape (8 .dp))
95+ .aspectRatio(1f )
96+ .clickable {
97+ color = item
98+ onColorChange(item)
99+ colorSwatchIndex = index
100+ colorSelectionIndex.mainSelection = 0
101+ colorSelectionIndex.subSelection = index
102+ md3Tones = getColorTonesList(item)
103+ },
104+ selected = (colorSelectionIndex.mainSelection == 0 &&
105+ colorSelectionIndex.subSelection == index) || (
106+ colorSelectionIndex.mainSelection == 1 &&
107+ colorSelectionIndex.subSelection == 5 &&
108+ index == colorSwatchIndex
109+ ),
110+ backgroundColor = item
111+ )
112+ }
113+ }
114+
115+ Text (
116+ text = " Material Design2 Shade" ,
117+ modifier = Modifier
118+ .fillMaxWidth()
119+ .padding(2 .dp),
120+ textAlign = TextAlign .Center ,
121+ fontSize = 16 .sp,
122+ fontWeight = FontWeight .Bold ,
123+ color = Color .White
124+ )
125+
126+ LazyVerticalGrid (
127+ columns = GridCells .Fixed (8 ),
128+ contentPadding = PaddingValues (8 .dp),
129+ verticalArrangement = Arrangement .spacedBy(4 .dp),
130+ horizontalArrangement = Arrangement .spacedBy(4 .dp),
131+ ) {
132+ itemsIndexed(colors) { index: Int , item: Color ->
133+ ColorDisplayWithTitle (
134+ modifier = Modifier
135+ .clip(RoundedCornerShape (8 .dp))
136+ .aspectRatio(1f )
137+ .clickable {
138+ color = item
139+ colorSelectionIndex.mainSelection = 1
140+ colorSelectionIndex.subSelection = index
141+ onColorChange(item)
142+ md3Tones = getColorTonesList(item)
143+ },
144+ selected = (colorSelectionIndex.mainSelection == 0 && index == 5 )
145+ || (colorSelectionIndex.mainSelection == 1
146+ && colorSelectionIndex.subSelection == index),
147+ backgroundColor = item,
148+ contentColor = if (index < 5 ) Color .Black else Color .White ,
149+ title = keys[index].toString(),
150+ )
151+ }
152+ }
153+
154+ Text (
155+ text = " Material Design3 Tonal Palette" ,
156+ modifier = Modifier
157+ .fillMaxWidth()
158+ .padding(2 .dp),
159+ textAlign = TextAlign .Center ,
160+ fontSize = 16 .sp,
161+ fontWeight = FontWeight .Bold ,
162+ color = Color .White
163+ )
164+
165+ LazyVerticalGrid (
166+ columns = GridCells .Fixed (8 ),
167+ contentPadding = PaddingValues (8 .dp),
168+ verticalArrangement = Arrangement .spacedBy(4 .dp),
169+ horizontalArrangement = Arrangement .spacedBy(4 .dp),
170+ ) {
171+ itemsIndexed(md3Tones) { index: Int , item: Color ->
172+ ColorDisplayWithTitle (
173+ modifier = Modifier
174+ .aspectRatio(1f )
175+ .clip(RoundedCornerShape (8 .dp))
176+ .aspectRatio(1f )
177+ .clickable {
178+ colorSelectionIndex.mainSelection = 2
179+ colorSelectionIndex.subSelection = index
180+ color = item
181+ onColorChange(item)
182+ },
183+ backgroundColor = item,
184+ selected = (colorSelectionIndex.mainSelection == 2
185+ && colorSelectionIndex.subSelection == index),
186+ contentColor = if (index < 6 ) Color .White else Color .Black ,
187+ title = material3ToneRange[index].toString()
188+ )
189+ }
190+ }
191+
192+ Spacer (modifier = Modifier .height(30 .dp))
193+ val lightness = colorToHSL(color)[2 ]
194+ val textColor = if (lightness < .6f ) Color .White else Color .Black
195+
196+
197+ Text (
198+ text = colorName,
199+ modifier = Modifier
200+ .fillMaxWidth()
201+ .padding(2 .dp),
202+ textAlign = TextAlign .Center ,
203+ fontSize = 20 .sp,
204+ fontWeight = FontWeight .Bold ,
205+ color = Color .White
206+ )
207+
208+
209+ Row (
210+ modifier = Modifier
211+ .padding(8 .dp)
212+ .background(color = color, RoundedCornerShape (50 ))
213+ .padding(horizontal = 20 .dp, vertical = 10 .dp),
214+ verticalAlignment = Alignment .CenterVertically
215+ ) {
216+
217+ val hexText = colorToHex(color = color)
218+ Text (
219+ text = hexText,
220+ fontSize = 24 .sp,
221+ color = textColor
222+ )
223+ Spacer (modifier = Modifier .width(20 .dp))
224+ IconButton (onClick = {
225+ Toast .makeText(context, " Copied $hexText " , Toast .LENGTH_SHORT ).show()
226+ clipboardManager.setText(AnnotatedString (hexText))
227+ }) {
228+ Icon (
229+ tint = textColor,
230+ painter = painterResource(id = R .drawable.ic_baseline_content_copy_24),
231+ contentDescription = " clipboard"
232+ )
233+ }
234+ }
235+
236+ }
237+ }
238+
239+ @Composable
240+ fun ColorDisplayWithTitle (
241+ modifier : Modifier ,
242+ title : String = "",
243+ selected : Boolean ,
244+ contentColor : Color = Color .Unspecified ,
245+ backgroundColor : Color
246+ ) {
247+ Box (
248+ contentAlignment = Alignment .Center
249+ ) {
250+ Box (
251+ modifier = modifier
252+ .background(backgroundColor)
253+ )
254+
255+ Text (text = title, color = contentColor, fontSize = 16 .sp)
256+
257+ if (selected) {
258+ Icon (
259+ modifier = modifier
260+ .background(contentColor.copy(alpha = .5f ))
261+ .padding(4 .dp),
262+ imageVector = Icons .Default .Check ,
263+ contentDescription = " check" ,
264+ tint = Color .Green
265+ )
266+ }
267+ }
268+ }
269+
270+ @Composable
271+ fun ColorDisplayWithIcon (
272+ modifier : Modifier ,
273+ selected : Boolean ,
274+ contentColor : Color = Color .Unspecified ,
275+ backgroundColor : Color
276+ ) {
277+ Box (
278+ contentAlignment = Alignment .Center
279+ ) {
280+ Box (
281+ modifier = modifier
282+ .background(backgroundColor)
283+ )
284+
285+ if (selected) {
286+ Icon (
287+ modifier = modifier
288+ .background(contentColor.copy(alpha = .5f ))
289+ .padding(4 .dp),
290+ imageVector = Icons .Default .Check ,
291+ contentDescription = " check" ,
292+ tint = Color .Green
293+ )
294+ }
295+ }
296+ }
297+
298+ data class ColorSelectionIndex (var mainSelection : Int = 0 , var subSelection : Int = 0 )
0 commit comments