Add terminal input onboarding bubble

This commit is contained in:
Joe Tretter
2026-05-05 19:54:33 -05:00
parent 9d09eb1385
commit 344ff99f0a
3 changed files with 60 additions and 2 deletions

View File

@@ -19,8 +19,8 @@ android {
applicationId = "solutions.tretter.githugandroid" applicationId = "solutions.tretter.githugandroid"
minSdk = 26 minSdk = 26
targetSdk = 35 targetSdk = 35
versionCode = 113 versionCode = 114
versionName = "0.1.112" versionName = "0.1.113"
testInstrumentationRunner = "androidx.test.runner.AndroidJUnitRunner" testInstrumentationRunner = "androidx.test.runner.AndroidJUnitRunner"
vectorDrawables.useSupportLibrary = true vectorDrawables.useSupportLibrary = true

View File

@@ -77,6 +77,7 @@ fun GitHugApp() {
var gitMessageEditorState by remember { mutableStateOf<GitMessageEditorState?>(null) } var gitMessageEditorState by remember { mutableStateOf<GitMessageEditorState?>(null) }
var manPageState by remember { mutableStateOf<ManPageState?>(null) } var manPageState by remember { mutableStateOf<ManPageState?>(null) }
var solvedCelebrationTitle by remember { mutableStateOf<String?>(null) } var solvedCelebrationTitle by remember { mutableStateOf<String?>(null) }
var showTerminalInputHint by remember { mutableStateOf(true) }
val currentLevel = levels[currentLevelIndex] val currentLevel = levels[currentLevelIndex]
LaunchedEffect(solvedCelebrationTitle) { LaunchedEffect(solvedCelebrationTitle) {
@@ -412,6 +413,7 @@ fun GitHugApp() {
val raw = submittedText.trim() val raw = submittedText.trim()
if (raw.isBlank()) return if (raw.isBlank()) return
showTerminalInputHint = false
suppressedImeEcho = submittedText suppressedImeEcho = submittedText
clearCommandInput(recreateField = true) clearCommandInput(recreateField = true)
@@ -509,6 +511,8 @@ fun GitHugApp() {
output = output, output = output,
inputFieldVersion = inputFieldVersion, inputFieldVersion = inputFieldVersion,
commandInput = commandInput, commandInput = commandInput,
showInputHint = showTerminalInputHint,
onInputHintDismiss = { showTerminalInputHint = false },
onValueChange = { onValueChange = {
val blockedEcho = suppressedImeEcho val blockedEcho = suppressedImeEcho
if (blockedEcho != null && commandInput.text.isEmpty()) { if (blockedEcho != null && commandInput.text.isEmpty()) {
@@ -518,6 +522,9 @@ fun GitHugApp() {
} }
} }
suppressedImeEcho = null suppressedImeEcho = null
if (it.text.isNotEmpty()) {
showTerminalInputHint = false
}
commandInput = it commandInput = it
}, },
onRun = { runCommand() }, onRun = { runCommand() },

View File

@@ -1,6 +1,7 @@
package solutions.tretter.githugandroid package solutions.tretter.githugandroid
import androidx.compose.foundation.ExperimentalFoundationApi import androidx.compose.foundation.ExperimentalFoundationApi
import androidx.compose.foundation.Canvas
import androidx.compose.foundation.background import androidx.compose.foundation.background
import androidx.compose.foundation.horizontalScroll import androidx.compose.foundation.horizontalScroll
import androidx.compose.foundation.layout.Arrangement import androidx.compose.foundation.layout.Arrangement
@@ -37,6 +38,7 @@ import androidx.compose.ui.focus.FocusRequester
import androidx.compose.ui.focus.focusRequester import androidx.compose.ui.focus.focusRequester
import androidx.compose.ui.focus.onFocusChanged import androidx.compose.ui.focus.onFocusChanged
import androidx.compose.ui.graphics.Color import androidx.compose.ui.graphics.Color
import androidx.compose.ui.graphics.Path
import androidx.compose.ui.graphics.SolidColor import androidx.compose.ui.graphics.SolidColor
import androidx.compose.ui.platform.LocalConfiguration import androidx.compose.ui.platform.LocalConfiguration
import androidx.compose.ui.platform.LocalFocusManager import androidx.compose.ui.platform.LocalFocusManager
@@ -57,6 +59,8 @@ fun TerminalPane(
output: List<String>, output: List<String>,
inputFieldVersion: Int, inputFieldVersion: Int,
commandInput: TextFieldValue, commandInput: TextFieldValue,
showInputHint: Boolean,
onInputHintDismiss: () -> Unit,
onValueChange: (TextFieldValue) -> Unit, onValueChange: (TextFieldValue) -> Unit,
onRun: () -> Unit, onRun: () -> Unit,
onTab: () -> Unit, onTab: () -> Unit,
@@ -150,6 +154,9 @@ fun TerminalPane(
onHistoryDown = onHistoryDown, onHistoryDown = onHistoryDown,
onInsertText = { insertLiteral(it) }, onInsertText = { insertLiteral(it) },
) )
if (showInputHint) {
TerminalInputHintBubble()
}
Row( Row(
modifier = Modifier modifier = Modifier
.fillMaxWidth() .fillMaxWidth()
@@ -171,6 +178,7 @@ fun TerminalPane(
onValueChange = onValueChange, onValueChange = onValueChange,
focusRequester = focusRequester, focusRequester = focusRequester,
keyboardController = keyboardController, keyboardController = keyboardController,
onFocused = onInputHintDismiss,
onSubmit = { submitCommand() }, onSubmit = { submitCommand() },
) )
} }
@@ -186,6 +194,7 @@ private fun RowScope.TerminalInputField(
onValueChange: (TextFieldValue) -> Unit, onValueChange: (TextFieldValue) -> Unit,
focusRequester: FocusRequester, focusRequester: FocusRequester,
keyboardController: androidx.compose.ui.platform.SoftwareKeyboardController?, keyboardController: androidx.compose.ui.platform.SoftwareKeyboardController?,
onFocused: () -> Unit,
onSubmit: () -> Unit, onSubmit: () -> Unit,
) { ) {
key(inputFieldVersion) { key(inputFieldVersion) {
@@ -197,6 +206,7 @@ private fun RowScope.TerminalInputField(
.focusRequester(focusRequester) .focusRequester(focusRequester)
.onFocusChanged { state -> .onFocusChanged { state ->
if (state.isFocused) { if (state.isFocused) {
onFocused()
keyboardController?.show() keyboardController?.show()
} }
}, },
@@ -237,6 +247,47 @@ private fun RowScope.TerminalInputField(
} }
} }
@Composable
private fun TerminalInputHintBubble() {
val bubbleColor = Color(0xFFFFF1A8)
Column(
modifier = Modifier
.fillMaxWidth()
.padding(start = 46.dp, end = 14.dp),
horizontalAlignment = Alignment.Start,
) {
Box(
modifier = Modifier
.background(bubbleColor, RoundedCornerShape(18.dp))
.padding(horizontal = 14.dp, vertical = 10.dp),
) {
Text(
text = "Tap here to enter a command",
color = Color(0xFF161000),
fontFamily = FontFamily.Default,
fontWeight = FontWeight.Bold,
fontSize = 14.sp,
)
}
Canvas(
modifier = Modifier
.padding(start = 26.dp)
.width(26.dp)
.height(13.dp),
) {
drawPath(
path = Path().apply {
moveTo(0f, 0f)
lineTo(size.width, 0f)
lineTo(size.width * 0.25f, size.height)
close()
},
color = bubbleColor,
)
}
}
}
@Composable @Composable
private fun SpecialKeyBar( private fun SpecialKeyBar(
onTab: () -> Unit, onTab: () -> Unit,