Skip to content

Commit aaeadb0

Browse files
authored
Merge pull request #498 from synonymdev/feat/tabbar-v59
TabBar v59
2 parents c06c035 + 686d303 commit aaeadb0

File tree

2 files changed

+75
-65
lines changed

2 files changed

+75
-65
lines changed

app/src/main/java/to/bitkit/ui/ContentView.kt

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -457,7 +457,6 @@ fun ContentView(
457457

458458
if (showTabBar) {
459459
TabBar(
460-
hazeState = hazeState,
461460
onSendClick = { appViewModel.showSheet(Sheet.Send()) },
462461
onReceiveClick = { appViewModel.showSheet(Sheet.Receive) },
463462
onScanClick = { navController.navigateToScanner() },

app/src/main/java/to/bitkit/ui/components/TabBar.kt

Lines changed: 75 additions & 64 deletions
Original file line numberDiff line numberDiff line change
@@ -20,29 +20,32 @@ import androidx.compose.material.icons.Icons
2020
import androidx.compose.material.icons.filled.ArrowDownward
2121
import androidx.compose.material.icons.filled.ArrowUpward
2222
import androidx.compose.material3.Icon
23+
import androidx.compose.material3.MaterialTheme
2324
import androidx.compose.runtime.Composable
2425
import androidx.compose.ui.Alignment
2526
import androidx.compose.ui.Modifier
2627
import androidx.compose.ui.draw.clip
28+
import androidx.compose.ui.draw.drawWithContent
29+
import androidx.compose.ui.draw.shadow
30+
import androidx.compose.ui.geometry.Offset
2731
import androidx.compose.ui.graphics.Brush
2832
import androidx.compose.ui.graphics.Color
33+
import androidx.compose.ui.graphics.drawscope.Stroke
2934
import androidx.compose.ui.platform.testTag
3035
import androidx.compose.ui.res.painterResource
3136
import androidx.compose.ui.res.stringResource
3237
import androidx.compose.ui.tooling.preview.Preview
3338
import androidx.compose.ui.unit.dp
34-
import dev.chrisbanes.haze.HazeState
35-
import dev.chrisbanes.haze.hazeEffect
3639
import dev.chrisbanes.haze.hazeSource
37-
import dev.chrisbanes.haze.materials.CupertinoMaterials
3840
import dev.chrisbanes.haze.materials.ExperimentalHazeMaterialsApi
3941
import dev.chrisbanes.haze.rememberHazeState
4042
import to.bitkit.R
43+
import to.bitkit.ui.shared.util.clickableAlpha
44+
import to.bitkit.ui.shared.util.gradientBackground
45+
import to.bitkit.ui.shared.util.primaryButtonStyle
4146
import to.bitkit.ui.theme.AppThemeSurface
4247
import to.bitkit.ui.theme.Colors
4348

44-
private val buttonBg = Color(40, 40, 40).copy(alpha = 0.95f)
45-
private val buttonBgOverlay = Brush.verticalGradient(colors = listOf(buttonBg, Color.Transparent))
4649

4750
private val iconToTextGap = 4.dp
4851
private val iconSize = 20.dp
@@ -54,7 +57,6 @@ private val buttonRightShape = RoundedCornerShape(topEndPercent = 50, bottomEndP
5457
@Composable
5558
fun BoxScope.TabBar(
5659
modifier: Modifier = Modifier,
57-
hazeState: HazeState = rememberHazeState(),
5860
onSendClick: () -> Unit = {},
5961
onReceiveClick: () -> Unit = {},
6062
onScanClick: () -> Unit = {},
@@ -64,37 +66,23 @@ fun BoxScope.TabBar(
6466
modifier = modifier
6567
.align(Alignment.BottomCenter)
6668
.fillMaxWidth()
67-
.background(
68-
Brush.verticalGradient(
69-
colorStops = arrayOf(
70-
0.0f to Color.Transparent,
71-
0.5f to Colors.Black,
72-
),
73-
)
74-
)
7569
.padding(horizontal = 16.dp)
7670
.padding(bottom = 16.dp)
7771
.navigationBarsPadding()
7872
) {
79-
Row {
80-
val buttonMaterial = CupertinoMaterials.ultraThin(containerColor = buttonBg)
73+
Row(
74+
modifier = Modifier.primaryButtonStyle(
75+
isEnabled = true,
76+
shape = MaterialTheme.shapes.large,
77+
)
78+
) {
8179
// Send Button
8280
Box(
8381
contentAlignment = Alignment.Center,
8482
modifier = Modifier
8583
.weight(1f)
8684
.height(60.dp)
8785
.clip(buttonLeftShape)
88-
.background(buttonBg) // fallback if no haze
89-
.hazeEffect(
90-
state = hazeState,
91-
style = buttonMaterial,
92-
)
93-
.background(
94-
Brush.verticalGradient(
95-
colors = listOf(buttonBg, Color.Transparent),
96-
)
97-
)
9886
.clickable { onSendClick() }
9987
.testTag("Send")
10088
) {
@@ -116,12 +104,6 @@ fun BoxScope.TabBar(
116104
.weight(1f)
117105
.height(60.dp)
118106
.clip(buttonRightShape)
119-
.background(buttonBg) // fallback if no haze
120-
.hazeEffect(
121-
state = hazeState,
122-
style = buttonMaterial,
123-
)
124-
.background(buttonBgOverlay)
125107
.clickable { onReceiveClick() }
126108
.testTag("Receive")
127109
) {
@@ -137,45 +119,74 @@ fun BoxScope.TabBar(
137119
}
138120
}
139121

140-
// Scan Button
141-
val scanBg = Colors.Gray6.copy(alpha = 0.75f)
142-
// Outer Border
122+
// Scan button
143123
Box(
144-
content = {},
145124
contentAlignment = Alignment.Center,
146125
modifier = Modifier
147-
.size(80.dp)
148-
.clip(CircleShape)
149-
.background(buttonBg) // fallback if no haze
150-
.hazeEffect(
151-
state = hazeState,
152-
style = CupertinoMaterials.regular(containerColor = buttonBg),
153-
)
154-
.background(
155-
Brush.verticalGradient(colors = listOf(Colors.White10, Color.Transparent))
126+
.size(64.dp)
127+
// Shadow 1: gray2 shadow with radius 0 at y=-1 (top highlight)
128+
.drawWithContent {
129+
// Draw a prominent top highlight
130+
drawCircle(
131+
color = Colors.Gray2,
132+
radius = size.width / 2,
133+
center = Offset(size.width / 2, size.height / 2 - 1.5.dp.toPx()),
134+
alpha = 0.6f
135+
)
136+
drawContent()
137+
}
138+
// Shadow 2: black 25% opacity, radius 25, y offset 20
139+
.shadow(
140+
elevation = 25.dp,
141+
shape = CircleShape,
142+
ambientColor = Colors.Black25,
143+
spotColor = Colors.Black25
156144
)
157-
.clickable { onScanClick() }
158-
.padding(2.dp)
159-
.testTag("Scan")
160-
)
161-
// Inner Content
162-
Box(
163-
contentAlignment = Alignment.Center,
164-
modifier = Modifier
165-
.size(76.dp)
166145
.clip(CircleShape)
167-
.background(buttonBg) // fallback if no haze
168-
.hazeEffect(
169-
state = hazeState,
170-
style = CupertinoMaterials.regular(containerColor = Color.Transparent),
171-
)
172-
.background(scanBg)
146+
.background(Colors.Gray7)
147+
// Overlay: Circle strokeBorder with linear gradient mask (iOS: .mask)
148+
.drawWithContent {
149+
drawContent()
150+
151+
// The mask gradient goes from black (visible) at top to clear (invisible) at bottom
152+
val borderWidth = 2.dp.toPx()
153+
154+
// Create vertical gradient mask (black to clear)
155+
val maskGradient = Brush.verticalGradient(
156+
colors = listOf(
157+
Color.White, // Top: full opacity (shows border)
158+
Color.Transparent // Bottom: transparent (hides border)
159+
),
160+
startY = 0f,
161+
endY = size.height
162+
)
163+
164+
// Draw solid black circular border first, then apply gradient as alpha mask
165+
drawCircle(
166+
color = Color.Black,
167+
radius = (size.width - borderWidth) / 2,
168+
center = Offset(size.width / 2, size.height / 2),
169+
style = Stroke(width = borderWidth),
170+
alpha = 1f
171+
)
172+
173+
// Apply gradient mask by drawing gradient as overlay with BlendMode
174+
drawCircle(
175+
brush = maskGradient,
176+
radius = (size.width - borderWidth) / 2,
177+
center = Offset(size.width / 2, size.height / 2),
178+
style = Stroke(width = borderWidth),
179+
blendMode = androidx.compose.ui.graphics.BlendMode.DstIn
180+
)
181+
}
182+
.clickableAlpha { onScanClick() }
183+
.testTag("Scan")
173184
) {
174185
Icon(
175186
painter = painterResource(R.drawable.ic_scan),
176187
contentDescription = stringResource(R.string.wallet__recipient_scan),
177-
tint = Colors.Gray2,
178-
modifier = Modifier.size(32.dp)
188+
tint = Colors.Gray1,
189+
modifier = Modifier.size(22.dp)
179190
)
180191
}
181192
}
@@ -190,7 +201,8 @@ private fun Preview() {
190201
contentAlignment = Alignment.BottomCenter,
191202
modifier = Modifier
192203
.fillMaxSize()
193-
.background(Colors.Brand)
204+
.background(Colors.Black)
205+
.gradientBackground()
194206
) {
195207
// Content Behind
196208
Column(
@@ -205,7 +217,6 @@ private fun Preview() {
205217
onSendClick = {},
206218
onReceiveClick = {},
207219
onScanClick = {},
208-
hazeState = hazeState,
209220
)
210221
}
211222
}

0 commit comments

Comments
 (0)