Skip to content

Commit f6a33fc

Browse files
committed
improved UI loading in crash screens
1 parent 01bbb45 commit f6a33fc

7 files changed

Lines changed: 324 additions & 167 deletions

File tree

app/lint-baseline.xml

Lines changed: 16 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -247,14 +247,28 @@
247247
id="TrustAllX509TrustManager"
248248
message="`checkClientTrusted` is empty, which could cause insecure network traffic due to trusting arbitrary TLS/SSL certificates presented by peers">
249249
<location
250-
file="$GRADLE_USER_HOME/caches/9.5.0/transforms/685e69f775ffd85e1dd7a6757751e0c9/transformed/out/jars/classes.jar"/>
250+
file="$GRADLE_USER_HOME/caches/9.5.0/transforms/8e1d5edde7dd1e5761ee9f0f31eb4b89/transformed/out/jars/classes.jar"/>
251251
</issue>
252252

253253
<issue
254254
id="TrustAllX509TrustManager"
255255
message="`checkServerTrusted` is empty, which could cause insecure network traffic due to trusting arbitrary TLS/SSL certificates presented by peers">
256256
<location
257-
file="$GRADLE_USER_HOME/caches/9.5.0/transforms/685e69f775ffd85e1dd7a6757751e0c9/transformed/out/jars/classes.jar"/>
257+
file="$GRADLE_USER_HOME/caches/9.5.0/transforms/8e1d5edde7dd1e5761ee9f0f31eb4b89/transformed/out/jars/classes.jar"/>
258+
</issue>
259+
260+
<issue
261+
id="TrustAllX509TrustManager"
262+
message="`checkClientTrusted` is empty, which could cause insecure network traffic due to trusting arbitrary TLS/SSL certificates presented by peers">
263+
<location
264+
file="$GRADLE_USER_HOME/caches/modules-2/files-2.1/com.google.http-client/google-http-client/2.1.0/f4c08fa160a5199af19f81080fbaa921ae2193c7/google-http-client-2.1.0.jar"/>
265+
</issue>
266+
267+
<issue
268+
id="TrustAllX509TrustManager"
269+
message="`checkServerTrusted` is empty, which could cause insecure network traffic due to trusting arbitrary TLS/SSL certificates presented by peers">
270+
<location
271+
file="$GRADLE_USER_HOME/caches/modules-2/files-2.1/com.google.http-client/google-http-client/2.1.0/f4c08fa160a5199af19f81080fbaa921ae2193c7/google-http-client-2.1.0.jar"/>
258272
</issue>
259273

260274
<issue

app/src/main/java/in/hridayan/ashell/core/presentation/components/scrollbar/DraggableScrollThumb.kt

Lines changed: 104 additions & 50 deletions
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,9 @@ package `in`.hridayan.ashell.core.presentation.components.scrollbar
33
import androidx.compose.animation.core.Spring
44
import androidx.compose.animation.core.animateFloatAsState
55
import androidx.compose.animation.core.spring
6+
import androidx.compose.foundation.ScrollState
67
import androidx.compose.foundation.gestures.detectVerticalDragGestures
8+
import androidx.compose.foundation.gestures.scrollBy
79
import androidx.compose.foundation.layout.Box
810
import androidx.compose.foundation.layout.fillMaxHeight
911
import androidx.compose.foundation.layout.size
@@ -17,6 +19,7 @@ import androidx.compose.runtime.getValue
1719
import androidx.compose.runtime.mutableFloatStateOf
1820
import androidx.compose.runtime.mutableStateOf
1921
import androidx.compose.runtime.remember
22+
import androidx.compose.runtime.rememberCoroutineScope
2023
import androidx.compose.runtime.setValue
2124
import androidx.compose.ui.Modifier
2225
import androidx.compose.ui.draw.drawWithContent
@@ -27,52 +30,122 @@ import androidx.compose.ui.layout.onSizeChanged
2730
import androidx.compose.ui.platform.LocalDensity
2831
import androidx.compose.ui.unit.dp
2932
import kotlinx.coroutines.delay
33+
import kotlinx.coroutines.launch
3034

3135
/**
3236
* A draggable circular scroll thumb that enables smooth fast-scrolling on a [LazyListState].
33-
*
34-
* Uses [dispatchRawDelta] for synchronous, jitter-free scrolling during drag.
3537
*/
3638
@Composable
3739
fun DraggableScrollThumb(
3840
listState: LazyListState,
3941
modifier: Modifier = Modifier,
4042
thumbSize: Int = 48
4143
) {
42-
val thumbSizeDp = thumbSize.dp
43-
val density = LocalDensity.current
44-
val thumbSizePx = with(density) { thumbSizeDp.toPx() }
45-
46-
var isDragging by remember { mutableStateOf(false) }
47-
var trackHeightPx by remember { mutableFloatStateOf(0f) }
48-
var showThumb by remember { mutableStateOf(false) }
49-
var capturedScrollableRange by remember { mutableFloatStateOf(0f) }
44+
var stableAvgSize by remember { mutableFloatStateOf(0f) }
5045

51-
// Improved scroll progress calculation for LazyList
5246
val scrollProgress by remember {
5347
derivedStateOf {
5448
val info = listState.layoutInfo
5549
if (info.totalItemsCount == 0 || info.visibleItemsInfo.isEmpty()) 0f
5650
else {
5751
val firstVisibleItem = info.visibleItemsInfo.first()
58-
val lastVisibleItem = info.visibleItemsInfo.last()
5952
val totalItemsCount = info.totalItemsCount
53+
val viewportHeight = info.viewportEndOffset - info.viewportStartOffset
54+
55+
val visibleItems = info.visibleItemsInfo
56+
val currentAvg = visibleItems.map { it.size }.average().toFloat()
57+
58+
if (stableAvgSize == 0f) stableAvgSize = currentAvg
59+
60+
val estimatedTotalHeight = stableAvgSize * totalItemsCount
61+
val currentScrollOffset = (firstVisibleItem.index * stableAvgSize) - firstVisibleItem.offset
62+
val totalScrollableRange = (estimatedTotalHeight - viewportHeight).coerceAtLeast(1f)
63+
(currentScrollOffset / totalScrollableRange).coerceIn(0f, 1f)
64+
}
65+
}
66+
}
6067

61-
val firstIndex = firstVisibleItem.index
62-
val lastIndex = lastVisibleItem.index
63-
val visibleCount = lastIndex - firstIndex + 1
64-
65-
val offset = -firstVisibleItem.offset.toFloat()
66-
val size = firstVisibleItem.size.toFloat()
67-
val fraction = if (size > 0) offset / size else 0f
68+
LaunchedEffect(listState.isScrollInProgress) {
69+
if (listState.isScrollInProgress) {
70+
val info = listState.layoutInfo
71+
if (info.visibleItemsInfo.isNotEmpty()) {
72+
val currentAvg = info.visibleItemsInfo.map { it.size }.average().toFloat()
73+
stableAvgSize = if (stableAvgSize == 0f) currentAvg else stableAvgSize * 0.95f + currentAvg * 0.05f
74+
}
75+
}
76+
}
6877

69-
val progress =
70-
(firstIndex + fraction) / (totalItemsCount - visibleCount + 1).coerceAtLeast(1)
71-
progress.coerceIn(0f, 1f)
78+
DraggableScrollThumbImpl(
79+
scrollProgress = scrollProgress,
80+
isScrollInProgress = listState.isScrollInProgress,
81+
onDrag = { deltaPx, trackRange ->
82+
val info = listState.layoutInfo
83+
val vis = info.visibleItemsInfo
84+
if (vis.isNotEmpty()) {
85+
val avgSize = vis.map { it.size }.average().toFloat()
86+
val totalContentHeight = avgSize * info.totalItemsCount
87+
val viewportHeight = (info.viewportEndOffset - info.viewportStartOffset).toFloat()
88+
val scrollableRange = (totalContentHeight - viewportHeight).coerceAtLeast(1f)
89+
90+
val scrollDelta = deltaPx * (scrollableRange / trackRange)
91+
listState.dispatchRawDelta(scrollDelta)
7292
}
93+
},
94+
modifier = modifier,
95+
thumbSize = thumbSize
96+
)
97+
}
98+
99+
/**
100+
* A draggable circular scroll thumb for [ScrollState] (regular Column/Row).
101+
*/
102+
@Composable
103+
fun DraggableScrollThumb(
104+
scrollState: ScrollState,
105+
modifier: Modifier = Modifier,
106+
thumbSize: Int = 48
107+
) {
108+
val scrollProgress by remember {
109+
derivedStateOf {
110+
if (scrollState.maxValue == 0) 0f
111+
else (scrollState.value.toFloat() / scrollState.maxValue).coerceIn(0f, 1f)
73112
}
74113
}
75114

115+
val coroutineScope = rememberCoroutineScope()
116+
117+
DraggableScrollThumbImpl(
118+
scrollProgress = scrollProgress,
119+
isScrollInProgress = scrollState.isScrollInProgress,
120+
onDrag = { deltaPx, trackRange ->
121+
if (scrollState.maxValue > 0) {
122+
val scrollDelta = deltaPx * (scrollState.maxValue.toFloat() / trackRange)
123+
coroutineScope.launch {
124+
scrollState.scrollBy(scrollDelta)
125+
}
126+
}
127+
},
128+
modifier = modifier,
129+
thumbSize = thumbSize
130+
)
131+
}
132+
133+
@Composable
134+
private fun DraggableScrollThumbImpl(
135+
scrollProgress: Float,
136+
isScrollInProgress: Boolean,
137+
onDrag: (deltaPx: Float, trackRange: Float) -> Unit,
138+
modifier: Modifier = Modifier,
139+
thumbSize: Int = 48
140+
) {
141+
val thumbSizeDp = thumbSize.dp
142+
val density = LocalDensity.current
143+
val thumbSizePx = with(density) { thumbSizeDp.toPx() }
144+
145+
var isDragging by remember { mutableStateOf(false) }
146+
var trackHeightPx by remember { mutableFloatStateOf(0f) }
147+
var showThumb by remember { mutableStateOf(false) }
148+
76149
var dragProgress by remember { mutableFloatStateOf(0f) }
77150
LaunchedEffect(scrollProgress, isDragging) {
78151
if (!isDragging) {
@@ -89,21 +162,21 @@ fun DraggableScrollThumb(
89162
label = "thumbProgress"
90163
)
91164

92-
val thumbOffsetY = remember(animatedProgress, trackHeightPx) {
93-
(animatedProgress * (trackHeightPx - thumbSizePx)).coerceIn(
165+
val effectiveProgress = if (isDragging) dragProgress else animatedProgress
166+
167+
val thumbOffsetY = remember(effectiveProgress, trackHeightPx) {
168+
(effectiveProgress * (trackHeightPx - thumbSizePx)).coerceIn(
94169
0f,
95170
(trackHeightPx - thumbSizePx).coerceAtLeast(0f)
96171
)
97172
}
98173

99-
// Horizontal slide animation
100174
val thumbOffsetX by animateFloatAsState(
101175
targetValue = if (showThumb || isDragging) 0f else thumbSizePx,
102176
animationSpec = spring(stiffness = Spring.StiffnessLow),
103177
label = "thumbOffsetX"
104178
)
105179

106-
// Scale animation on drag
107180
val thumbScale by animateFloatAsState(
108181
targetValue = if (isDragging) 0.8f else 1f,
109182
animationSpec = spring(stiffness = Spring.StiffnessMedium),
@@ -116,8 +189,8 @@ fun DraggableScrollThumb(
116189
label = "thumbAlpha"
117190
)
118191

119-
LaunchedEffect(listState.isScrollInProgress, isDragging) {
120-
if (listState.isScrollInProgress || isDragging) {
192+
LaunchedEffect(isScrollInProgress, isDragging) {
193+
if (isScrollInProgress || isDragging) {
121194
showThumb = true
122195
} else {
123196
delay(1500)
@@ -145,36 +218,17 @@ fun DraggableScrollThumb(
145218
}
146219
.pointerInput(trackHeightPx) {
147220
detectVerticalDragGestures(
148-
onDragStart = {
149-
isDragging = true
150-
// Lock the scroll range at start of drag to prevent jumping
151-
val info = listState.layoutInfo
152-
val vis = info.visibleItemsInfo
153-
if (vis.isNotEmpty()) {
154-
val avgSize = vis.map { it.size }.average().toFloat()
155-
val totalContentHeight = avgSize * info.totalItemsCount
156-
val viewportHeight =
157-
(info.viewportEndOffset - info.viewportStartOffset).toFloat()
158-
capturedScrollableRange =
159-
(totalContentHeight - viewportHeight).coerceAtLeast(1f)
160-
}
161-
},
221+
onDragStart = { isDragging = true },
162222
onDragEnd = { isDragging = false },
163223
onDragCancel = { isDragging = false },
164224
onVerticalDrag = { change, dragAmount ->
165225
change.consume()
166226

167227
val trackRange = (trackHeightPx - thumbSizePx).coerceAtLeast(1f)
168-
val newProgress =
169-
(dragProgress + dragAmount / trackRange).coerceIn(0f, 1f)
228+
val newProgress = (dragProgress + dragAmount / trackRange).coerceIn(0f, 1f)
170229
dragProgress = newProgress
171230

172-
// Map track movement to scroll movement using the locked range
173-
if (capturedScrollableRange > 0) {
174-
val scrollDelta =
175-
dragAmount * (capturedScrollableRange / trackRange)
176-
listState.dispatchRawDelta(scrollDelta)
177-
}
231+
onDrag(dragAmount, trackRange)
178232
}
179233
)
180234
}

app/src/main/java/in/hridayan/ashell/crashreporter/presentation/screens/CrashReportScreen.kt

Lines changed: 29 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -13,9 +13,7 @@ import androidx.compose.foundation.layout.fillMaxWidth
1313
import androidx.compose.foundation.layout.height
1414
import androidx.compose.foundation.layout.padding
1515
import androidx.compose.foundation.lazy.LazyColumn
16-
import androidx.compose.foundation.shape.RoundedCornerShape
17-
import androidx.compose.foundation.text.selection.SelectionContainer
18-
import androidx.compose.material3.Card
16+
import androidx.compose.foundation.lazy.items
1917
import androidx.compose.material3.CardDefaults
2018
import androidx.compose.material3.ExperimentalMaterial3Api
2119
import androidx.compose.material3.ExperimentalMaterial3ExpressiveApi
@@ -204,29 +202,43 @@ fun CrashReportScreen(
204202
start = 20.dp,
205203
end = 20.dp,
206204
top = 25.dp,
205+
bottom = 10.dp
207206
)
208207
)
208+
}
209+
210+
stacktrace?.let { trace ->
211+
val lines = trace.split("\n")
212+
items(lines.size) { index ->
213+
val line = lines[index]
214+
215+
val cardShape = when (index) {
216+
0 -> CustomCardShape(top = 24.dp, bottom = 0.dp)
217+
lines.lastIndex -> CustomCardShape(top = 0.dp, bottom = 24.dp)
218+
else -> CustomCardShape(0.dp)
219+
}
209220

210-
stacktrace?.let {
211221
CustomCard(
212-
modifier = Modifier
213-
.fillMaxWidth()
214-
.padding(15.dp),
222+
modifier = Modifier.fillMaxWidth().padding(horizontal = 15.dp),
223+
shape = cardShape,
215224
colors = CardDefaults.cardColors(
216225
containerColor = MaterialTheme.colorScheme.surfaceContainer,
217226
contentColor = MaterialTheme.colorScheme.onSurface
218227
)
219228
) {
220-
SelectionContainer {
221-
Text(
222-
text = it,
223-
modifier = Modifier
224-
.fillMaxWidth()
225-
.padding(horizontal = 20.dp, vertical = 15.dp),
226-
style = MaterialTheme.typography.bodySmallEmphasized,
227-
fontFamily = FontFamily.Monospace,
228-
)
229-
}
229+
Text(
230+
text = line,
231+
modifier = Modifier
232+
.fillMaxWidth()
233+
.padding(
234+
start = 20.dp,
235+
end = 20.dp,
236+
top = if (index == 0) 20.dp else 2.dp,
237+
bottom = if (index == lines.lastIndex) 20.dp else 2.dp
238+
),
239+
style = MaterialTheme.typography.bodySmallEmphasized,
240+
fontFamily = FontFamily.Monospace,
241+
)
230242
}
231243
}
232244
}

0 commit comments

Comments
 (0)