Skip to content

Commit e586c9b

Browse files
committed
style(bills): update punch coloring logic
Signed-off-by: Brandon McAnsh <git@bmcreations.dev>
1 parent e3063d4 commit e586c9b

2 files changed

Lines changed: 146 additions & 124 deletions

File tree

  • apps/flipcash/shared/bills/src/main/kotlin/com/flipcash/app/bills
  • ui/core/src/main/kotlin/com/getcode/ui/core

apps/flipcash/shared/bills/src/main/kotlin/com/flipcash/app/bills/CashBill.kt

Lines changed: 140 additions & 93 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
package com.flipcash.app.bills
22

3+
import android.R.attr.fillType
34
import android.annotation.SuppressLint
45
import android.graphics.Bitmap
56
import android.graphics.BitmapFactory
@@ -32,29 +33,46 @@ import androidx.compose.runtime.remember
3233
import androidx.compose.ui.Alignment
3334
import androidx.compose.ui.Modifier
3435
import androidx.compose.ui.draw.alpha
36+
import androidx.compose.ui.draw.clip
3537
import androidx.compose.ui.draw.clipToBounds
38+
import androidx.compose.ui.draw.drawBehind
39+
import androidx.compose.ui.draw.drawWithContent
3640
import androidx.compose.ui.draw.rotate
3741
import androidx.compose.ui.geometry.Offset
42+
import androidx.compose.ui.geometry.Rect
43+
import androidx.compose.ui.geometry.Size
3844
import androidx.compose.ui.graphics.BlendMode
3945
import androidx.compose.ui.graphics.Brush
4046
import androidx.compose.ui.graphics.Color
47+
import androidx.compose.ui.graphics.Color.Companion.Black
4148
import androidx.compose.ui.graphics.ColorFilter
49+
import androidx.compose.ui.graphics.CompositingStrategy
4250
import androidx.compose.ui.graphics.ImageBitmap
51+
import androidx.compose.ui.graphics.Outline
52+
import androidx.compose.ui.graphics.Path
53+
import androidx.compose.ui.graphics.PathFillType
54+
import androidx.compose.ui.graphics.Shape
55+
import androidx.compose.ui.graphics.SolidColor
4356
import androidx.compose.ui.graphics.asImageBitmap
4457
import androidx.compose.ui.graphics.compositeOver
4558
import androidx.compose.ui.graphics.drawscope.DrawScope
59+
import androidx.compose.ui.graphics.drawscope.clipPath
60+
import androidx.compose.ui.graphics.graphicsLayer
61+
import androidx.compose.ui.graphics.lerp
4662
import androidx.compose.ui.layout.ContentScale
4763
import androidx.compose.ui.platform.LocalContext
4864
import androidx.compose.ui.platform.LocalResources
4965
import androidx.compose.ui.res.imageResource
5066
import androidx.compose.ui.res.painterResource
5167
import androidx.compose.ui.text.TextStyle
5268
import androidx.compose.ui.text.font.FontWeight
69+
import androidx.compose.ui.unit.Density
5370
import androidx.compose.ui.unit.Dp
5471
import androidx.compose.ui.unit.DpOffset
5572
import androidx.compose.ui.unit.DpSize
5673
import androidx.compose.ui.unit.IntOffset
5774
import androidx.compose.ui.unit.IntSize
75+
import androidx.compose.ui.unit.LayoutDirection
5876
import androidx.compose.ui.unit.TextUnit
5977
import androidx.compose.ui.unit.dp
6078
import androidx.compose.ui.unit.isSpecified
@@ -76,6 +94,7 @@ import com.getcode.ui.core.punchRectangle
7694
import com.getcode.ui.utils.Geometry
7795
import com.getcode.ui.utils.deriveTargetColor
7896
import com.getcode.ui.utils.hexToColor
97+
import com.getcode.ui.utils.hls
7998
import com.getcode.ui.utils.nonScaledSp
8099
import kotlin.math.ceil
81100
import kotlin.math.roundToInt
@@ -127,7 +146,7 @@ private object CashBillDefaults {
127146
fun punchBrushIn(punch: Punch, token: Token): Brush {
128147
val billCustomizations = token.billCustomizations
129148
if (billCustomizations?.background == null) {
130-
val color = Color.Black.copy(0.15f)
149+
val color = Black.copy(0.15f)
131150
.compositeOver(CodeTheme.colors.cashBillColor.copy(alpha = CodeBackgroundOpacity))
132151
return Brush.verticalGradient(
133152
colors = listOf(color, color)
@@ -138,7 +157,7 @@ private object CashBillDefaults {
138157
is BillBackground.Gradient -> {
139158
when (punch) {
140159
Punch.SecurityStrip -> {
141-
val color = Color.Black.copy(0.15f)
160+
val color = Black.copy(0.15f)
142161
.compositeOver(
143162
hexToColor(bg.colors.first())
144163
.copy(alpha = CodeBackgroundOpacity)
@@ -150,30 +169,15 @@ private object CashBillDefaults {
150169
}
151170

152171
Punch.Code -> {
153-
val bgColors = if (bg.colors.size == 3) {
154-
bg.colors.slice(listOf(0, 2))
155-
} else {
156-
bg.colors
157-
}
158-
159-
Brush.verticalGradient(
160-
colors = bgColors.map { hexToColor(it) }
161-
.map {
162-
deriveTargetColor(
163-
sourceColor = it,
164-
targetLightness = -0.314f,
165-
targetSaturation = -0.203f
166-
).copy(alpha = 0.62f)
167-
}
168-
)
172+
punchBrushFrom(bg.colors.map { hexToColor(it) })
169173
}
170174
}
171175
}
172176

173177
is BillBackground.Solid -> {
174178
when (punch) {
175179
Punch.SecurityStrip -> {
176-
val color = Color.Black.copy(0.15f)
180+
val color = Black.copy(0.15f)
177181
.compositeOver(hexToColor(bg.colorHex).copy(alpha = 0.62f))
178182

179183
Brush.verticalGradient(
@@ -182,15 +186,7 @@ private object CashBillDefaults {
182186
}
183187

184188
Punch.Code -> {
185-
val color = deriveTargetColor(
186-
sourceColor = hexToColor(bg.colorHex),
187-
targetLightness = -0.314f,
188-
targetSaturation = -0.203f
189-
).copy(alpha = 0.62f)
190-
191-
Brush.verticalGradient(
192-
colors = listOf(color, color)
193-
)
189+
punchBrushFrom( listOf(hexToColor(bg.colorHex)))
194190
}
195191
}
196192
}
@@ -313,86 +309,103 @@ internal fun CashBill(
313309
.aspectRatio(CashBillDefaults.AspectRatio, matchHeightConstraintsFirst = true)
314310
.fillMaxHeight()
315311
.fillMaxWidth(0.95f)
316-
.background(CashBillDefaults.billColor(token))
317312
.clipToBounds()
318313
) {
319314
val geometry = remember(maxWidth, maxHeight) {
320315
CashBillGeometry(maxWidth, maxHeight)
321316
}
322317

323-
if (hasCustomTexture) {
324-
val context = LocalContext.current
325-
val resources = LocalResources.current
326-
val pattern = runCatching {
327-
resources.getIdentifier("bill_texture_${customTexture.index}", "drawable", context.packageName)
328-
}.getOrNull()
318+
val punchShape = remember(geometry) {
319+
BillPunchShape(codeSize = geometry.codeSize)
320+
}
329321

330-
if (pattern != null) {
331-
Box(
332-
modifier = Modifier
333-
.fillMaxSize()
334-
.patternBlend(
335-
pattern = ImageBitmap.imageResource(resources, pattern),
336-
blendMode = when (customTexture.blendMode) {
337-
PlaygroundBlendMode.Normal -> null
338-
PlaygroundBlendMode.Lighten -> BlendMode.Lighten
339-
PlaygroundBlendMode.Screen -> BlendMode.Screen
340-
PlaygroundBlendMode.ColorDodge -> BlendMode.ColorDodge
341-
PlaygroundBlendMode.PlusLighter -> BlendMode.Softlight
342-
},
343-
strength = customTexture.strength,
344-
),
345-
)
346-
}
347-
} else {
348-
// Hexagons
349-
BillDecorImage(
350-
modifier = Modifier
351-
.fillMaxSize(),
352-
image = loadBillAsset(R.drawable.ic_bill_hexagons),
353-
blendMode = BlendMode.Multiply,
354-
alpha = 0.6f,
355-
)
322+
// Background with punch hole
323+
Box(
324+
modifier = Modifier
325+
.fillMaxSize()
326+
.background(CashBillDefaults.billColor(token), punchShape)
327+
)
356328

357-
// Grid pattern
358-
BillDecorImage(
359-
modifier = Modifier
360-
.fillMaxSize(),
361-
image = loadBillAsset(R.drawable.ic_bill_grid),
362-
size = DpSize(width = geometry.gridWidth, height = geometry.gridHeight),
363-
topLeft = Offset(
364-
x = geometry.gridPosition.x,
365-
y = geometry.gridPosition.y,
329+
// Texture layers — clipped with even-odd punch
330+
Box(
331+
modifier = Modifier
332+
.fillMaxSize()
333+
.clip(punchShape)
334+
) {
335+
if (hasCustomTexture) {
336+
val context = LocalContext.current
337+
val resources = LocalResources.current
338+
val pattern = runCatching {
339+
resources.getIdentifier("bill_texture_${customTexture.index}", "drawable", context.packageName)
340+
}.getOrNull()
341+
342+
if (pattern != null) {
343+
Box(
344+
modifier = Modifier
345+
.fillMaxSize()
346+
.patternBlend(
347+
pattern = ImageBitmap.imageResource(resources, pattern),
348+
blendMode = when (customTexture.blendMode) {
349+
PlaygroundBlendMode.Normal -> null
350+
PlaygroundBlendMode.Lighten -> BlendMode.Lighten
351+
PlaygroundBlendMode.Screen -> BlendMode.Screen
352+
PlaygroundBlendMode.ColorDodge -> BlendMode.ColorDodge
353+
PlaygroundBlendMode.PlusLighter -> BlendMode.Softlight
354+
},
355+
strength = customTexture.strength,
356+
),
357+
)
358+
}
359+
} else {
360+
// Hexagons
361+
BillDecorImage(
362+
modifier = Modifier
363+
.fillMaxSize(),
364+
image = loadBillAsset(R.drawable.ic_bill_hexagons),
365+
blendMode = BlendMode.Multiply,
366+
alpha = 0.6f,
366367
)
367-
)
368-
}
369368

370-
// Globe
371-
Image(
372-
modifier = Modifier
373-
.fillMaxHeight()
374-
.requiredWidth(geometry.globeWidth)
375-
.offset {
376-
IntOffset(
377-
x = geometry.globePosition.x.toInt(),
378-
y = geometry.globePosition.y.toInt()
369+
// Grid pattern
370+
BillDecorImage(
371+
modifier = Modifier
372+
.fillMaxSize(),
373+
image = loadBillAsset(R.drawable.ic_bill_grid),
374+
size = DpSize(width = geometry.gridWidth, height = geometry.gridHeight),
375+
topLeft = Offset(
376+
x = geometry.gridPosition.x,
377+
y = geometry.gridPosition.y,
379378
)
380-
},
381-
painter = painterResource(R.drawable.ic_bill_globe),
382-
contentDescription = null
383-
)
379+
)
380+
}
384381

385-
if (!hasCustomTexture) {
386-
// Waves
382+
// Globe
387383
Image(
388384
modifier = Modifier
389-
.requiredWidth(geometry.globeWidth)
390385
.fillMaxHeight()
391-
.offset { IntOffset(x = geometry.wavesPosition.x.toInt(), y = 0) },
392-
contentDescription = null,
393-
contentScale = ContentScale.FillBounds,
394-
painter = painterResource(R.drawable.ic_bill_waves),
386+
.requiredWidth(geometry.globeWidth)
387+
.offset {
388+
IntOffset(
389+
x = geometry.globePosition.x.toInt(),
390+
y = geometry.globePosition.y.toInt()
391+
)
392+
},
393+
painter = painterResource(R.drawable.ic_bill_globe),
394+
contentDescription = null
395395
)
396+
397+
if (!hasCustomTexture) {
398+
// Waves
399+
Image(
400+
modifier = Modifier
401+
.requiredWidth(geometry.globeWidth)
402+
.fillMaxHeight()
403+
.offset { IntOffset(x = geometry.wavesPosition.x.toInt(), y = 0) },
404+
contentDescription = null,
405+
contentScale = ContentScale.FillBounds,
406+
painter = painterResource(R.drawable.ic_bill_waves),
407+
)
408+
}
396409
}
397410

398411
// Security strip
@@ -580,7 +593,6 @@ private fun BillCode(
580593
modifier = modifier
581594
.punchCircle(
582595
brush = CashBillDefaults.punchBrushIn(punch = Punch.Code, token),
583-
// blendMode = BlendMode.SrcOver,
584596
),
585597
contentAlignment = Alignment.Center
586598
) {
@@ -605,3 +617,38 @@ private fun loadBillAsset(drawableRes: Int): ImageBitmap {
605617
option
606618
).asImageBitmap()
607619
}
620+
621+
private class BillPunchShape(
622+
private val codeSize: Dp,
623+
) : Shape {
624+
override fun createOutline(
625+
size: Size, layoutDirection: LayoutDirection, density: Density
626+
): Outline {
627+
val radius = with(density) { (codeSize / 2f).toPx() }
628+
val path = Path().apply {
629+
fillType = PathFillType.EvenOdd
630+
addRect(Rect(Offset.Zero, size))
631+
addOval(
632+
Rect(
633+
center = Offset(size.width / 2f, size.height / 2f),
634+
radius = radius
635+
)
636+
)
637+
}
638+
return Outline.Generic(path)
639+
}
640+
}
641+
642+
fun punchColorsFrom(billColors: List<Color>): List<Color> {
643+
return billColors.map { color ->
644+
lerp(color, Color.Black, 0.30f)
645+
}
646+
}
647+
648+
fun punchBrushFrom(billColors: List<Color>): Brush {
649+
val punched = punchColorsFrom(billColors)
650+
return when {
651+
punched.size == 1 -> SolidColor(punched[0])
652+
else -> Brush.verticalGradient(punched.map { it.copy(0.83f) })
653+
}
654+
}

0 commit comments

Comments
 (0)