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.
This commit is contained in:
@@ -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
|
||||
|
||||
@@ -11,7 +11,8 @@
|
||||
android:theme="@style/Theme.GithugAndroid">
|
||||
<activity
|
||||
android:name=".MainActivity"
|
||||
android:exported="true">
|
||||
android:exported="true"
|
||||
android:windowSoftInputMode="adjustResize">
|
||||
<intent-filter>
|
||||
<action android:name="android.intent.action.MAIN" />
|
||||
<category android:name="android.intent.category.LAUNCHER" />
|
||||
|
||||
@@ -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")
|
||||
}
|
||||
},
|
||||
)
|
||||
}
|
||||
@@ -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<Long>()) }
|
||||
var exerciseBounds by remember { mutableStateOf<Rect?>(null) }
|
||||
var promptBounds by remember { mutableStateOf<Rect?>(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)
|
||||
}
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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),
|
||||
) {
|
||||
|
||||
@@ -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)
|
||||
|
||||
Reference in New Issue
Block a user