From 618e6e36bdc0ef17d39f7cbb41799ddf0944843e Mon Sep 17 00:00:00 2001 From: Joe Tretter Date: Wed, 13 May 2026 18:35:36 -0500 Subject: [PATCH] Add hidden diagnostic skin toggle - Open a hidden diagnostics dialog after tapping the header seven times within fifteen seconds. - Let the diagnostics dialog toggle the pane color/stripe skin on demand. - Restore the normal app background and pane colors by default. - Stop Scaffold from preserving the bottom navigation-bar inset while the keyboard is visible. - Apply bottom navigation padding only when the IME is not open. --- app/build.gradle.kts | 4 +- app/src/main/AndroidManifest.xml | 3 +- .../githugandroid/DiagnosticSkinDialog.kt | 42 ++++++++++++++ .../tretter/githugandroid/GitHugApp.kt | 57 ++++++++++++++++--- .../tretter/githugandroid/GitHugTheme.kt | 3 +- .../solutions/tretter/githugandroid/Header.kt | 8 ++- .../solutions/tretter/githugandroid/Panes.kt | 46 ++++++++++++--- 7 files changed, 142 insertions(+), 21 deletions(-) create mode 100644 app/src/main/java/solutions/tretter/githugandroid/DiagnosticSkinDialog.kt diff --git a/app/build.gradle.kts b/app/build.gradle.kts index 92b5821..732fccb 100644 --- a/app/build.gradle.kts +++ b/app/build.gradle.kts @@ -19,8 +19,8 @@ android { applicationId = "solutions.tretter.githugandroid" minSdk = 26 targetSdk = 35 - versionCode = 142 - versionName = "0.1.141" + versionCode = 143 + versionName = "0.1.142" testInstrumentationRunner = "androidx.test.runner.AndroidJUnitRunner" vectorDrawables.useSupportLibrary = true diff --git a/app/src/main/AndroidManifest.xml b/app/src/main/AndroidManifest.xml index 745bcd7..26a1724 100644 --- a/app/src/main/AndroidManifest.xml +++ b/app/src/main/AndroidManifest.xml @@ -11,7 +11,8 @@ android:theme="@style/Theme.GithugAndroid"> + android:exported="true" + android:windowSoftInputMode="adjustResize"> diff --git a/app/src/main/java/solutions/tretter/githugandroid/DiagnosticSkinDialog.kt b/app/src/main/java/solutions/tretter/githugandroid/DiagnosticSkinDialog.kt new file mode 100644 index 0000000..06f0d84 --- /dev/null +++ b/app/src/main/java/solutions/tretter/githugandroid/DiagnosticSkinDialog.kt @@ -0,0 +1,42 @@ +package solutions.tretter.githugandroid + +import androidx.compose.foundation.layout.Arrangement +import androidx.compose.foundation.layout.Row +import androidx.compose.foundation.layout.fillMaxWidth +import androidx.compose.material3.AlertDialog +import androidx.compose.material3.Switch +import androidx.compose.material3.Text +import androidx.compose.material3.TextButton +import androidx.compose.runtime.Composable +import androidx.compose.ui.Alignment +import androidx.compose.ui.Modifier + +@Composable +fun DiagnosticSkinDialog( + diagnosticSkinEnabled: Boolean, + onDiagnosticSkinEnabledChange: (Boolean) -> Unit, + onDismiss: () -> Unit, +) { + AlertDialog( + onDismissRequest = onDismiss, + title = { Text("Diagnostics") }, + text = { + Row( + modifier = Modifier.fillMaxWidth(), + horizontalArrangement = Arrangement.SpaceBetween, + verticalAlignment = Alignment.CenterVertically, + ) { + Text("Pane color skin") + Switch( + checked = diagnosticSkinEnabled, + onCheckedChange = onDiagnosticSkinEnabledChange, + ) + } + }, + confirmButton = { + TextButton(onClick = onDismiss) { + Text("Close") + } + }, + ) +} diff --git a/app/src/main/java/solutions/tretter/githugandroid/GitHugApp.kt b/app/src/main/java/solutions/tretter/githugandroid/GitHugApp.kt index 909a600..bbbf241 100644 --- a/app/src/main/java/solutions/tretter/githugandroid/GitHugApp.kt +++ b/app/src/main/java/solutions/tretter/githugandroid/GitHugApp.kt @@ -1,14 +1,18 @@ package solutions.tretter.githugandroid +import android.os.SystemClock import androidx.compose.foundation.background import androidx.compose.foundation.layout.Arrangement import androidx.compose.foundation.layout.Box import androidx.compose.foundation.layout.Column +import androidx.compose.foundation.layout.WindowInsets import androidx.compose.foundation.layout.fillMaxSize import androidx.compose.foundation.layout.fillMaxWidth -import androidx.compose.foundation.layout.imePadding +import androidx.compose.foundation.layout.ime +import androidx.compose.foundation.layout.navigationBars import androidx.compose.foundation.layout.padding import androidx.compose.foundation.rememberScrollState +import androidx.compose.foundation.layout.statusBarsPadding import androidx.compose.foundation.verticalScroll import androidx.compose.material3.MaterialTheme import androidx.compose.material3.Scaffold @@ -26,6 +30,7 @@ import androidx.compose.ui.Modifier import androidx.compose.ui.geometry.Rect import androidx.compose.ui.platform.LocalConfiguration import androidx.compose.ui.platform.LocalContext +import androidx.compose.ui.platform.LocalDensity import androidx.compose.ui.text.TextRange import androidx.compose.ui.text.input.TextFieldValue import androidx.compose.ui.unit.dp @@ -37,6 +42,7 @@ fun GitHugApp() { MaterialTheme(colorScheme = GitHugColorScheme) { val context = LocalContext.current val configuration = LocalConfiguration.current + val density = LocalDensity.current val runtime = remember(context) { GitRepositoryRuntime(context.applicationContext) } if (!runtime.isNativeGitAvailable()) { MissingNativeGitScreen(message = runtime.unavailableMessage()) @@ -76,9 +82,18 @@ fun GitHugApp() { var showHelpOverlay by rememberSaveable { mutableStateOf(false) } var showHelpOnStart by rememberSaveable { mutableStateOf(true) } var helpPreferenceInitialized by rememberSaveable { mutableStateOf(false) } + var showDiagnosticDialog by rememberSaveable { mutableStateOf(false) } + var diagnosticSkinEnabled by rememberSaveable { mutableStateOf(false) } + var headerTapTimes by remember { mutableStateOf(emptyList()) } var exerciseBounds by remember { mutableStateOf(null) } var promptBounds by remember { mutableStateOf(null) } val currentLevel = levels[currentLevelIndex] + val appBackground = if (diagnosticSkinEnabled) DiagnosticAppBackground else AppBackground + val imeBottom = WindowInsets.ime.getBottom(density) + val navigationBottom = WindowInsets.navigationBars.getBottom(density) + val bottomSystemPadding = with(density) { + if (imeBottom > navigationBottom) 0.dp else navigationBottom.toDp() + } LaunchedEffect(solvedCelebrationTitle) { if (solvedCelebrationTitle != null) { @@ -465,30 +480,50 @@ fun GitHugApp() { output = output + runtime.commandReferenceLines() } - Scaffold(containerColor = AppBackground) { padding -> + fun registerHeaderTap() { + val now = SystemClock.elapsedRealtime() + val recentTaps = (headerTapTimes + now).filter { tapTime -> now - tapTime <= 15_000L } + if (recentTaps.size >= 7) { + headerTapTimes = emptyList() + showDiagnosticDialog = true + } else { + headerTapTimes = recentTaps + } + } + + Scaffold( + containerColor = appBackground, + contentWindowInsets = WindowInsets(0.dp), + ) { padding -> Surface( modifier = Modifier .fillMaxSize() .padding(padding), - color = AppBackground, + color = appBackground, ) { Box(modifier = Modifier.fillMaxSize()) { Column( modifier = Modifier .fillMaxSize() - .background(AppBackground) - .imePadding() + .background(appBackground) + .statusBarsPadding() .verticalScroll(screenScrollState) - .padding(horizontal = 10.dp, vertical = 8.dp), + .padding( + start = 10.dp, + top = 8.dp, + end = 10.dp, + bottom = 8.dp + bottomSystemPadding, + ), verticalArrangement = Arrangement.spacedBy(8.dp), ) { - FixedHeader() + FixedHeader(onTap = { registerHeaderTap() }) Box( modifier = Modifier.fillMaxWidth(), ) { PaneWorkspace( modifier = Modifier.fillMaxWidth(), paneLayout = paneLayout, + diagnosticSkinEnabled = diagnosticSkinEnabled, onMovePane = { paneId, delta -> updatePaneLayout(transform = { layout -> movePane(layout, paneId, delta) }) }, @@ -623,6 +658,14 @@ fun GitHugApp() { ) } + if (showDiagnosticDialog) { + DiagnosticSkinDialog( + diagnosticSkinEnabled = diagnosticSkinEnabled, + onDiagnosticSkinEnabledChange = { diagnosticSkinEnabled = it }, + onDismiss = { showDiagnosticDialog = false }, + ) + } + solvedCelebrationTitle?.let { title -> SolvedCelebrationOverlay(title = title) } diff --git a/app/src/main/java/solutions/tretter/githugandroid/GitHugTheme.kt b/app/src/main/java/solutions/tretter/githugandroid/GitHugTheme.kt index 17e09ac..f9f6bb0 100644 --- a/app/src/main/java/solutions/tretter/githugandroid/GitHugTheme.kt +++ b/app/src/main/java/solutions/tretter/githugandroid/GitHugTheme.kt @@ -6,7 +6,8 @@ import androidx.compose.ui.graphics.Color val PanelPrimary = Color(0xFF121212) val PanelSecondary = Color(0xFF1E1E1E) val PanelTertiary = Color(0xFF262626) -val AppBackground = Color(0xFF2D1745) +val AppBackground = PanelSecondary +val DiagnosticAppBackground = Color(0xFF2D1745) val TerminalBackground = Color(0xFF050505) val TextPrimary = Color(0xFFFFFFFF) val TextSecondary = Color(0xFFE6E6E6) diff --git a/app/src/main/java/solutions/tretter/githugandroid/Header.kt b/app/src/main/java/solutions/tretter/githugandroid/Header.kt index f430022..2c29766 100644 --- a/app/src/main/java/solutions/tretter/githugandroid/Header.kt +++ b/app/src/main/java/solutions/tretter/githugandroid/Header.kt @@ -1,6 +1,7 @@ package solutions.tretter.githugandroid import androidx.compose.foundation.Image +import androidx.compose.foundation.clickable import androidx.compose.foundation.layout.Arrangement import androidx.compose.foundation.layout.Row import androidx.compose.foundation.layout.fillMaxWidth @@ -18,8 +19,11 @@ import androidx.compose.ui.res.painterResource import androidx.compose.ui.unit.dp @Composable -fun FixedHeader() { +fun FixedHeader(onTap: () -> Unit = {}) { Card( + modifier = Modifier + .fillMaxWidth() + .clickable(onClick = onTap), colors = CardDefaults.cardColors(containerColor = PanelPrimary), shape = RoundedCornerShape(14.dp), ) { @@ -38,4 +42,4 @@ fun FixedHeader() { Text("GitHug Android", style = MaterialTheme.typography.titleLarge, color = TextPrimary) } } -} \ No newline at end of file +} diff --git a/app/src/main/java/solutions/tretter/githugandroid/Panes.kt b/app/src/main/java/solutions/tretter/githugandroid/Panes.kt index 34c1536..531252f 100644 --- a/app/src/main/java/solutions/tretter/githugandroid/Panes.kt +++ b/app/src/main/java/solutions/tretter/githugandroid/Panes.kt @@ -29,6 +29,7 @@ import androidx.compose.ui.unit.sp fun PaneWorkspace( modifier: Modifier = Modifier, paneLayout: PaneLayout, + diagnosticSkinEnabled: Boolean = false, onMovePane: (PaneId, Int) -> Unit, onTogglePane: (PaneId) -> Unit, levelsContent: @Composable () -> Unit, @@ -47,6 +48,7 @@ fun PaneWorkspace( if (isCollapsed) { CollapsedPaneCard( paneId = paneId, + diagnosticSkinEnabled = diagnosticSkinEnabled, canMoveUp = index > 0, canMoveDown = index < paneLayout.order.lastIndex, onMoveUp = { onMovePane(paneId, -1) }, @@ -56,6 +58,7 @@ fun PaneWorkspace( } else { PaneCard( paneId = paneId, + diagnosticSkinEnabled = diagnosticSkinEnabled, modifier = Modifier .fillMaxWidth() .wrapContentHeight(), @@ -80,6 +83,7 @@ fun PaneWorkspace( @Composable private fun PaneCard( paneId: PaneId, + diagnosticSkinEnabled: Boolean, modifier: Modifier = Modifier, canMoveUp: Boolean, canMoveDown: Boolean, @@ -90,18 +94,20 @@ private fun PaneCard( ) { Card( modifier = modifier, - colors = CardDefaults.cardColors(containerColor = paneDiagnosticBase(paneId)), + colors = CardDefaults.cardColors( + containerColor = if (diagnosticSkinEnabled) paneDiagnosticBase(paneId) else PanelSecondary, + ), shape = RoundedCornerShape(16.dp), ) { Column( modifier = Modifier .fillMaxWidth() .wrapContentHeight() - .background(paneDiagnosticBase(paneId)) - .paneDiagnosticPattern(paneId), + .diagnosticPaneBackground(diagnosticSkinEnabled, paneId, paneDiagnosticBase(paneId)), ) { PaneHeader( paneId = paneId, + diagnosticSkinEnabled = diagnosticSkinEnabled, collapsed = false, canMoveUp = canMoveUp, canMoveDown = canMoveDown, @@ -113,8 +119,7 @@ private fun PaneCard( modifier = Modifier .fillMaxWidth() .wrapContentHeight() - .background(paneDiagnosticContent(paneId)) - .paneDiagnosticPattern(paneId) + .diagnosticPaneBackground(diagnosticSkinEnabled, paneId, paneDiagnosticContent(paneId)) .padding(horizontal = 12.dp, vertical = 10.dp), ) { content() @@ -126,6 +131,7 @@ private fun PaneCard( @Composable private fun CollapsedPaneCard( paneId: PaneId, + diagnosticSkinEnabled: Boolean, canMoveUp: Boolean, canMoveDown: Boolean, onMoveUp: () -> Unit, @@ -134,11 +140,14 @@ private fun CollapsedPaneCard( ) { Card( modifier = Modifier.fillMaxWidth(), - colors = CardDefaults.cardColors(containerColor = paneDiagnosticBase(paneId)), + colors = CardDefaults.cardColors( + containerColor = if (diagnosticSkinEnabled) paneDiagnosticBase(paneId) else PanelSecondary, + ), shape = RoundedCornerShape(16.dp), ) { PaneHeader( paneId = paneId, + diagnosticSkinEnabled = diagnosticSkinEnabled, collapsed = true, canMoveUp = canMoveUp, canMoveDown = canMoveDown, @@ -152,6 +161,7 @@ private fun CollapsedPaneCard( @Composable private fun PaneHeader( paneId: PaneId, + diagnosticSkinEnabled: Boolean, collapsed: Boolean, canMoveUp: Boolean, canMoveDown: Boolean, @@ -162,8 +172,16 @@ private fun PaneHeader( Row( modifier = Modifier .fillMaxWidth() - .background(if (collapsed) paneDiagnosticCollapsed(paneId) else paneDiagnosticHeader(paneId)) - .paneDiagnosticPattern(paneId) + .background( + if (diagnosticSkinEnabled) { + if (collapsed) paneDiagnosticCollapsed(paneId) else paneDiagnosticHeader(paneId) + } else if (collapsed) { + PanelTertiary + } else { + PanelPrimary + } + ) + .diagnosticPanePattern(diagnosticSkinEnabled, paneId) .padding(horizontal = 10.dp, vertical = 6.dp), verticalAlignment = Alignment.CenterVertically, horizontalArrangement = Arrangement.SpaceBetween, @@ -200,6 +218,18 @@ private fun paneDiagnosticHeader(paneId: PaneId): Color = when (paneId) { private fun paneDiagnosticCollapsed(paneId: PaneId): Color = paneDiagnosticContent(paneId) +private fun Modifier.diagnosticPaneBackground(enabled: Boolean, paneId: PaneId, color: Color): Modifier { + return if (enabled) { + background(color).diagnosticPanePattern(enabled = true, paneId = paneId) + } else { + this + } +} + +private fun Modifier.diagnosticPanePattern(enabled: Boolean, paneId: PaneId): Modifier { + return if (enabled) paneDiagnosticPattern(paneId) else this +} + private fun Modifier.paneDiagnosticPattern(paneId: PaneId): Modifier = drawBehind { val stripeColor = when (paneId) { PaneId.EXERCISE -> Color(0x6684C5FF)