Skip to content

Commit 182e01f

Browse files
committed
feat: background blur
1 parent 0153136 commit 182e01f

File tree

1 file changed

+149
-17
lines changed

1 file changed

+149
-17
lines changed

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

Lines changed: 149 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -2,20 +2,23 @@ package to.bitkit.ui.components
22

33
import androidx.compose.animation.AnimatedContent
44
import androidx.compose.animation.SizeTransform
5+
import androidx.compose.animation.core.Animatable
6+
import androidx.compose.animation.core.Spring
7+
import androidx.compose.animation.core.spring
8+
import androidx.compose.animation.core.tween
59
import androidx.compose.animation.fadeIn
610
import androidx.compose.animation.fadeOut
711
import androidx.compose.animation.slideInVertically
812
import androidx.compose.animation.slideOutVertically
913
import androidx.compose.animation.togetherWith
1014
import androidx.compose.foundation.background
15+
import androidx.compose.foundation.gestures.detectDragGestures
1116
import androidx.compose.foundation.layout.Arrangement
1217
import androidx.compose.foundation.layout.Box
1318
import androidx.compose.foundation.layout.Column
14-
import androidx.compose.foundation.layout.Row
15-
import androidx.compose.foundation.layout.Spacer
1619
import androidx.compose.foundation.layout.fillMaxSize
1720
import androidx.compose.foundation.layout.fillMaxWidth
18-
import androidx.compose.foundation.layout.height
21+
import androidx.compose.foundation.layout.offset
1922
import androidx.compose.foundation.layout.padding
2023
import androidx.compose.foundation.layout.size
2124
import androidx.compose.foundation.layout.systemBarsPadding
@@ -26,59 +29,174 @@ import androidx.compose.material3.IconButton
2629
import androidx.compose.material3.MaterialTheme
2730
import androidx.compose.runtime.Composable
2831
import androidx.compose.runtime.ReadOnlyComposable
32+
import androidx.compose.runtime.getValue
33+
import androidx.compose.runtime.mutableStateOf
34+
import androidx.compose.runtime.remember
35+
import androidx.compose.runtime.rememberCoroutineScope
36+
import androidx.compose.runtime.setValue
2937
import androidx.compose.ui.Alignment
3038
import androidx.compose.ui.Modifier
39+
import androidx.compose.ui.draw.shadow
3140
import androidx.compose.ui.graphics.Color
41+
import androidx.compose.ui.input.pointer.pointerInput
3242
import androidx.compose.ui.platform.testTag
3343
import androidx.compose.ui.res.stringResource
3444
import androidx.compose.ui.tooling.preview.Preview
45+
import androidx.compose.ui.unit.IntOffset
3546
import androidx.compose.ui.unit.dp
47+
import kotlinx.coroutines.launch
3648
import to.bitkit.R
3749
import to.bitkit.models.Toast
3850
import to.bitkit.ui.scaffold.ScreenColumn
3951
import to.bitkit.ui.theme.AppThemeSurface
4052
import to.bitkit.ui.theme.Colors
53+
import kotlin.math.roundToInt
4154

4255
@Composable
4356
fun ToastView(
4457
toast: Toast,
4558
onDismiss: () -> Unit,
59+
onDragStart: () -> Unit = {},
60+
onDragEnd: () -> Unit = {},
4661
) {
4762
val tintColor = toast.tintColor()
63+
val coroutineScope = rememberCoroutineScope()
64+
val dragOffset = remember { Animatable(0f) }
65+
var hasPausedAutoHide by remember { mutableStateOf(false) }
66+
val dismissThreshold = 50.dp
4867

4968
Box(
50-
contentAlignment = Alignment.CenterStart,
69+
contentAlignment = Alignment.TopStart,
5170
modifier = Modifier
5271
.fillMaxWidth()
5372
.systemBarsPadding()
5473
.padding(horizontal = 16.dp)
55-
.background(tintColor.copy(alpha = 0.32f), shape = MaterialTheme.shapes.medium)
56-
.padding(16.dp)
5774
.then(toast.testTag?.let { Modifier.testTag(it) } ?: Modifier),
5875
) {
59-
Row(
60-
verticalAlignment = Alignment.CenterVertically,
61-
modifier = Modifier.fillMaxWidth()
76+
// Main toast content
77+
Box(
78+
modifier = Modifier
79+
.fillMaxWidth()
80+
.offset { IntOffset(0, dragOffset.value.roundToInt()) }
81+
.shadow(
82+
elevation = 10.dp,
83+
shape = MaterialTheme.shapes.medium,
84+
ambientColor = Color.Black.copy(alpha = 0.4f),
85+
spotColor = Color.Black.copy(alpha = 0.4f)
86+
)
87+
.background(
88+
color = MaterialTheme.colorScheme.surface.copy(alpha = 0.85f),
89+
shape = MaterialTheme.shapes.medium
90+
)
91+
.background(
92+
color = tintColor.copy(alpha = 0.32f),
93+
shape = MaterialTheme.shapes.medium
94+
)
95+
.pointerInput(Unit) {
96+
detectDragGestures(
97+
onDragStart = {
98+
// Drag started
99+
},
100+
onDragEnd = {
101+
// Resume auto-hide when drag ends (if we paused it)
102+
if (hasPausedAutoHide) {
103+
hasPausedAutoHide = false
104+
onDragEnd()
105+
}
106+
107+
coroutineScope.launch {
108+
// Dismiss if swiped up enough, otherwise snap back
109+
if (dragOffset.value < -dismissThreshold.toPx()) {
110+
// Animate out
111+
dragOffset.animateTo(
112+
targetValue = -200f,
113+
animationSpec = tween(durationMillis = 300)
114+
)
115+
onDismiss()
116+
} else {
117+
// Snap back to original position
118+
dragOffset.animateTo(
119+
targetValue = 0f,
120+
animationSpec = spring(
121+
dampingRatio = 0.7f,
122+
stiffness = Spring.StiffnessMedium
123+
)
124+
)
125+
}
126+
}
127+
},
128+
onDragCancel = {
129+
coroutineScope.launch {
130+
dragOffset.animateTo(
131+
targetValue = 0f,
132+
animationSpec = spring(
133+
dampingRatio = 0.7f,
134+
stiffness = Spring.StiffnessMedium
135+
)
136+
)
137+
}
138+
},
139+
onDrag = { change, dragAmount ->
140+
change.consume()
141+
coroutineScope.launch {
142+
val translation = dragOffset.value + dragAmount.y
143+
144+
if (translation < 0) {
145+
// Upward drag - allow freely
146+
dragOffset.snapTo(translation)
147+
} else {
148+
// Downward drag - apply resistance
149+
dragOffset.snapTo(translation * 0.08f)
150+
}
151+
152+
// Pause auto-hide when drag starts (only once)
153+
if (kotlin.math.abs(dragOffset.value) > 5 && !hasPausedAutoHide) {
154+
hasPausedAutoHide = true
155+
onDragStart()
156+
}
157+
}
158+
}
159+
)
160+
}
62161
) {
63-
Column(modifier = Modifier.weight(1f)) {
162+
Column(
163+
verticalArrangement = Arrangement.spacedBy(2.dp),
164+
modifier = Modifier
165+
.fillMaxWidth()
166+
.padding(16.dp)
167+
) {
64168
BodyMSB(
65169
text = toast.title,
66170
color = tintColor,
67171
)
68172
toast.description?.let { description ->
69-
Spacer(modifier = Modifier.height(8.dp))
70-
Caption(text = description)
173+
Caption(
174+
text = description,
175+
color = MaterialTheme.colorScheme.onSurface
176+
)
71177
}
72178
}
73-
if (!toast.autoHide) {
179+
}
180+
181+
// Close button overlay (top-trailing)
182+
if (!toast.autoHide) {
183+
Box(
184+
modifier = Modifier
185+
.fillMaxWidth()
186+
.offset { IntOffset(0, dragOffset.value.roundToInt()) },
187+
contentAlignment = Alignment.TopEnd
188+
) {
74189
IconButton(
75190
onClick = onDismiss,
76-
modifier = Modifier.size(24.dp)
191+
modifier = Modifier
192+
.size(48.dp)
193+
.padding(16.dp)
77194
) {
78195
Icon(
79196
imageVector = Icons.Default.Close,
80197
contentDescription = stringResource(R.string.common__close),
81-
tint = Color.White,
198+
tint = MaterialTheme.colorScheme.onSurfaceVariant,
199+
modifier = Modifier.size(16.dp)
82200
)
83201
}
84202
}
@@ -90,6 +208,8 @@ fun ToastView(
90208
private fun ToastHost(
91209
toast: Toast?,
92210
onDismiss: () -> Unit,
211+
onDragStart: () -> Unit = {},
212+
onDragEnd: () -> Unit = {},
93213
) {
94214
AnimatedContent(
95215
targetState = toast,
@@ -102,7 +222,12 @@ private fun ToastHost(
102222
label = "toastAnimation",
103223
) {
104224
if (it != null) {
105-
ToastView(toast = it, onDismiss = onDismiss)
225+
ToastView(
226+
toast = it,
227+
onDismiss = onDismiss,
228+
onDragStart = onDragStart,
229+
onDragEnd = onDragEnd
230+
)
106231
}
107232
}
108233
}
@@ -112,12 +237,19 @@ fun ToastOverlay(
112237
toast: Toast?,
113238
modifier: Modifier = Modifier,
114239
onDismiss: () -> Unit,
240+
onDragStart: () -> Unit = {},
241+
onDragEnd: () -> Unit = {},
115242
) {
116243
Box(
117244
contentAlignment = Alignment.TopCenter,
118245
modifier = modifier.fillMaxSize(),
119246
) {
120-
ToastHost(toast = toast, onDismiss = onDismiss)
247+
ToastHost(
248+
toast = toast,
249+
onDismiss = onDismiss,
250+
onDragStart = onDragStart,
251+
onDragEnd = onDragEnd
252+
)
121253
}
122254
}
123255

0 commit comments

Comments
 (0)