Skip to content

Commit 42f91c4

Browse files
committed
android: improve debugging
1 parent 33ba7a2 commit 42f91c4

2 files changed

Lines changed: 271 additions & 57 deletions

File tree

android/app/src/main/java/me/kavishdevar/aln/screens/DebugScreen.kt

Lines changed: 205 additions & 33 deletions
Original file line numberDiff line numberDiff line change
@@ -21,12 +21,18 @@
2121
package me.kavishdevar.aln.screens
2222

2323
import android.annotation.SuppressLint
24+
import android.content.ClipData
25+
import android.content.ClipboardManager
2426
import android.content.Context
2527
import android.os.Build
28+
import android.widget.Toast
2629
import androidx.annotation.RequiresApi
30+
import androidx.compose.foundation.ExperimentalFoundationApi
2731
import androidx.compose.foundation.background
2832
import androidx.compose.foundation.clickable
33+
import androidx.compose.foundation.combinedClickable
2934
import androidx.compose.foundation.isSystemInDarkTheme
35+
import androidx.compose.foundation.layout.Box
3036
import androidx.compose.foundation.layout.Column
3137
import androidx.compose.foundation.layout.ExperimentalLayoutApi
3238
import androidx.compose.foundation.layout.Row
@@ -43,10 +49,14 @@ import androidx.compose.foundation.shape.RoundedCornerShape
4349
import androidx.compose.material.icons.Icons
4450
import androidx.compose.material.icons.automirrored.filled.KeyboardArrowLeft
4551
import androidx.compose.material.icons.automirrored.filled.KeyboardArrowRight
52+
import androidx.compose.material.icons.filled.Delete
53+
import androidx.compose.material.icons.filled.MoreVert
4654
import androidx.compose.material.icons.filled.Send
4755
import androidx.compose.material3.Card
4856
import androidx.compose.material3.CardDefaults
4957
import androidx.compose.material3.CenterAlignedTopAppBar
58+
import androidx.compose.material3.DropdownMenu
59+
import androidx.compose.material3.DropdownMenuItem
5060
import androidx.compose.material3.ExperimentalMaterial3Api
5161
import androidx.compose.material3.HorizontalDivider
5262
import androidx.compose.material3.Icon
@@ -64,11 +74,13 @@ import androidx.compose.runtime.derivedStateOf
6474
import androidx.compose.runtime.getValue
6575
import androidx.compose.runtime.mutableStateOf
6676
import androidx.compose.runtime.remember
77+
import androidx.compose.runtime.rememberCoroutineScope
6778
import androidx.compose.ui.Alignment
6879
import androidx.compose.ui.Modifier
6980
import androidx.compose.ui.draw.scale
7081
import androidx.compose.ui.graphics.Color
7182
import androidx.compose.ui.platform.LocalContext
83+
import androidx.compose.ui.platform.LocalFocusManager
7284
import androidx.compose.ui.text.TextStyle
7385
import androidx.compose.ui.text.font.Font
7486
import androidx.compose.ui.text.font.FontFamily
@@ -83,10 +95,27 @@ import dev.chrisbanes.haze.hazeChild
8395
import dev.chrisbanes.haze.materials.CupertinoMaterials
8496
import dev.chrisbanes.haze.materials.ExperimentalHazeMaterialsApi
8597
import kotlinx.coroutines.flow.MutableStateFlow
98+
import kotlinx.coroutines.delay
99+
import kotlinx.coroutines.launch
100+
import kotlinx.coroutines.Dispatchers
101+
import kotlinx.coroutines.CoroutineScope
86102
import me.kavishdevar.aln.R
87103
import me.kavishdevar.aln.services.ServiceManager
88104
import me.kavishdevar.aln.utils.BatteryStatus
89105
import me.kavishdevar.aln.utils.isHeadTrackingData
106+
import me.kavishdevar.aln.composables.StyledSwitch
107+
import androidx.compose.foundation.layout.navigationBarsPadding
108+
import androidx.compose.foundation.layout.imePadding
109+
import androidx.compose.ui.geometry.Offset
110+
import androidx.compose.ui.input.pointer.pointerInput
111+
import androidx.compose.ui.platform.LocalDensity
112+
import androidx.compose.ui.layout.onGloballyPositioned
113+
import androidx.compose.ui.layout.positionInRoot
114+
import androidx.compose.ui.text.style.TextAlign
115+
import androidx.compose.foundation.gestures.detectTapGestures
116+
import androidx.compose.foundation.gestures.detectDragGestures
117+
import androidx.compose.material.icons.filled.Check
118+
import androidx.compose.ui.input.pointer.PointerInputChange
90119

91120
data class PacketInfo(
92121
val type: String,
@@ -286,39 +315,84 @@ fun parseOutgoingPacket(bytes: ByteArray, rawData: String): PacketInfo {
286315
}
287316
}
288317

318+
@Composable
319+
fun IOSCheckbox(
320+
checked: Boolean,
321+
onCheckedChange: (Boolean) -> Unit,
322+
modifier: Modifier = Modifier
323+
) {
324+
Box(
325+
modifier = modifier
326+
.size(24.dp)
327+
.clickable { onCheckedChange(!checked) },
328+
contentAlignment = Alignment.Center
329+
) {
330+
if (checked) {
331+
Icon(
332+
imageVector = Icons.Default.Check,
333+
contentDescription = "Checked",
334+
tint = if (isSystemInDarkTheme()) Color(0xFF007AFF) else Color(0xFF3C6DF5),
335+
modifier = Modifier.size(20.dp)
336+
)
337+
}
338+
}
339+
}
340+
289341
@RequiresApi(Build.VERSION_CODES.Q)
290-
@OptIn(ExperimentalMaterial3Api::class, ExperimentalLayoutApi::class)
342+
@OptIn(ExperimentalMaterial3Api::class, ExperimentalLayoutApi::class, ExperimentalFoundationApi::class)
291343
@SuppressLint("UnusedMaterial3ScaffoldPaddingParameter", "UnspecifiedRegisterReceiverFlag")
292344
@Composable
293345
fun DebugScreen(navController: NavController) {
294346
val hazeState = remember { HazeState() }
295347
val context = LocalContext.current
296348
val listState = rememberLazyListState()
297349
val scrollOffset by remember { derivedStateOf { listState.firstVisibleItemScrollOffset } }
298-
val packetLogsFlow = remember { MutableStateFlow(emptySet<String>()) }
350+
val focusManager = LocalFocusManager.current
351+
val coroutineScope = rememberCoroutineScope()
352+
353+
val showMenu = remember { mutableStateOf(false) }
354+
355+
val airPodsService = remember { ServiceManager.getService() }
356+
val packetLogs = airPodsService?.packetLogsFlow?.collectAsState(emptySet())?.value ?: emptySet()
357+
val shouldScrollToBottom = remember { mutableStateOf(true) }
358+
359+
val refreshTrigger = remember { mutableStateOf(0) }
360+
LaunchedEffect(refreshTrigger.value) {
361+
while(true) {
362+
delay(1000)
363+
refreshTrigger.value = refreshTrigger.value + 1
364+
}
365+
}
366+
299367
val expandedItems = remember { mutableStateOf(setOf<Int>()) }
300-
301-
LaunchedEffect(Unit) {
302-
ServiceManager.getService()?.packetLogsFlow?.collect { packetLogsFlow.value = it }
368+
369+
fun copyToClipboard(text: String) {
370+
val clipboard = context.getSystemService(Context.CLIPBOARD_SERVICE) as ClipboardManager
371+
val clip = ClipData.newPlainText("Packet Data", text)
372+
clipboard.setPrimaryClip(clip)
373+
Toast.makeText(context, "Packet copied to clipboard", Toast.LENGTH_SHORT).show()
374+
}
375+
376+
LaunchedEffect(packetLogs.size, refreshTrigger.value) {
377+
if (shouldScrollToBottom.value && packetLogs.isNotEmpty()) {
378+
listState.animateScrollToItem(packetLogs.size - 1)
379+
}
303380
}
304-
val packetLogs = packetLogsFlow.collectAsState(setOf()).value
305381

306382
Scaffold(
307383
topBar = {
308384
CenterAlignedTopAppBar(
309385
title = { Text("Debug") },
310386
navigationIcon = {
311387
TextButton(
312-
onClick = {
313-
navController.popBackStack()
314-
},
388+
onClick = { navController.popBackStack() },
315389
shape = RoundedCornerShape(8.dp),
316390
) {
317391
val sharedPreferences = context.getSharedPreferences("settings", Context.MODE_PRIVATE)
318392
Icon(
319393
Icons.AutoMirrored.Filled.KeyboardArrowLeft,
320394
contentDescription = "Back",
321-
tint = if (isSystemInDarkTheme()) Color(0xFF007AFF) else Color(0xFF3C6DF5),
395+
tint = if (isSystemInDarkTheme()) Color(0xFF007AFF) else Color(0xFF3C6DF5),
322396
modifier = Modifier.scale(1.5f)
323397
)
324398
Text(
@@ -332,31 +406,105 @@ fun DebugScreen(navController: NavController) {
332406
)
333407
}
334408
},
335-
modifier = Modifier
336-
.hazeChild(
337-
state = hazeState,
338-
style = CupertinoMaterials.thick(),
339-
block = {
340-
alpha = if (scrollOffset > 0) {
341-
1f
342-
} else {
343-
0f
344-
}
409+
actions = {
410+
Box {
411+
IconButton(onClick = { showMenu.value = true }) {
412+
Icon(
413+
imageVector = Icons.Default.MoreVert,
414+
contentDescription = "More Options",
415+
tint = if (isSystemInDarkTheme()) Color.White else Color.Black
416+
)
345417
}
346-
),
347-
colors = TopAppBarDefaults.topAppBarColors(
348-
containerColor = Color.Transparent
418+
419+
DropdownMenu(
420+
expanded = showMenu.value,
421+
onDismissRequest = { showMenu.value = false },
422+
modifier = Modifier
423+
.width(250.dp)
424+
.background(
425+
if (isSystemInDarkTheme()) Color(0xFF1C1B20) else Color(0xFFF2F2F7)
426+
)
427+
.padding(vertical = 4.dp)
428+
) {
429+
DropdownMenuItem(
430+
text = {
431+
Row(
432+
verticalAlignment = Alignment.CenterVertically,
433+
modifier = Modifier.fillMaxWidth()
434+
) {
435+
Text(
436+
"Auto-scroll",
437+
style = TextStyle(
438+
fontSize = 16.sp,
439+
fontWeight = FontWeight.Normal
440+
)
441+
)
442+
Spacer(modifier = Modifier.weight(1f))
443+
IOSCheckbox(
444+
checked = shouldScrollToBottom.value,
445+
onCheckedChange = { shouldScrollToBottom.value = it }
446+
)
447+
}
448+
},
449+
onClick = {
450+
shouldScrollToBottom.value = !shouldScrollToBottom.value
451+
showMenu.value = false
452+
}
453+
)
454+
455+
HorizontalDivider(
456+
color = if (isSystemInDarkTheme()) Color(0xFF3A3A3C) else Color(0xFFE5E5EA),
457+
thickness = 0.5.dp
458+
)
459+
460+
DropdownMenuItem(
461+
text = {
462+
Row(
463+
verticalAlignment = Alignment.CenterVertically,
464+
modifier = Modifier.fillMaxWidth()
465+
) {
466+
Text(
467+
"Clear logs",
468+
style = TextStyle(
469+
fontSize = 16.sp,
470+
fontWeight = FontWeight.Normal
471+
)
472+
)
473+
Spacer(modifier = Modifier.weight(1f))
474+
Icon(
475+
imageVector = Icons.Default.Delete,
476+
contentDescription = "Clear logs",
477+
tint = if (isSystemInDarkTheme()) Color(0xFF007AFF) else Color(0xFF3C6DF5)
478+
)
479+
}
480+
},
481+
onClick = {
482+
ServiceManager.getService()?.clearLogs()
483+
expandedItems.value = emptySet()
484+
showMenu.value = false
485+
}
486+
)
487+
}
488+
}
489+
},
490+
modifier = Modifier.hazeChild(
491+
state = hazeState,
492+
style = CupertinoMaterials.thick(),
493+
block = {
494+
alpha = if (scrollOffset > 0) 1f else 0f
495+
}
349496
),
497+
colors = TopAppBarDefaults.topAppBarColors(containerColor = Color.Transparent),
350498
)
351499
},
352-
containerColor = if (isSystemInDarkTheme()) Color(0xFF000000)
353-
else Color(0xFFF2F2F7),
500+
containerColor = if (isSystemInDarkTheme()) Color(0xFF000000) else Color(0xFFF2F2F7),
354501
) { paddingValues ->
355502
Column(
356503
modifier = Modifier
357504
.fillMaxSize()
358505
.haze(hazeState)
359506
.padding(top = paddingValues.calculateTopPadding())
507+
.navigationBarsPadding()
360508
) {
361509
LazyColumn(
362510
state = listState,
@@ -374,13 +522,18 @@ fun DebugScreen(navController: NavController) {
374522
modifier = Modifier
375523
.fillMaxWidth()
376524
.padding(vertical = 2.dp, horizontal = 4.dp)
377-
.clickable {
378-
expandedItems.value = if (isExpanded) {
379-
expandedItems.value - index
380-
} else {
381-
expandedItems.value + index
525+
.combinedClickable(
526+
onClick = {
527+
expandedItems.value = if (isExpanded) {
528+
expandedItems.value - index
529+
} else {
530+
expandedItems.value + index
531+
}
532+
},
533+
onLongClick = {
534+
copyToClipboard(packetInfo.rawData)
382535
}
383-
},
536+
),
384537
elevation = CardDefaults.cardElevation(defaultElevation = 2.dp),
385538
shape = RoundedCornerShape(4.dp),
386539
colors = CardDefaults.cardColors(
@@ -476,8 +629,27 @@ fun DebugScreen(navController: NavController) {
476629
trailingIcon = {
477630
IconButton(
478631
onClick = {
479-
airPodsService?.value?.sendPacket(packet.value.text)
480-
packet.value = TextFieldValue("")
632+
if (packet.value.text.isNotBlank()) {
633+
airPodsService?.value?.sendPacket(packet.value.text)
634+
packet.value = TextFieldValue("")
635+
focusManager.clearFocus()
636+
637+
if (shouldScrollToBottom.value && packetLogs.isNotEmpty()) {
638+
coroutineScope.launch {
639+
try {
640+
delay(100)
641+
listState.animateScrollToItem(
642+
index = (packetLogs.size - 1).coerceAtLeast(0),
643+
scrollOffset = 0
644+
)
645+
} catch (e: Exception) {
646+
listState.scrollToItem(
647+
index = (packetLogs.size - 1).coerceAtLeast(0)
648+
)
649+
}
650+
}
651+
}
652+
}
481653
}
482654
) {
483655
@Suppress("DEPRECATION")

0 commit comments

Comments
 (0)