diff --git a/app/build.gradle.kts b/app/build.gradle.kts index 512ae0a..46c1203 100644 --- a/app/build.gradle.kts +++ b/app/build.gradle.kts @@ -11,8 +11,8 @@ android { applicationId = "com.kawomi.githugandroid" minSdk = 26 targetSdk = 34 - versionCode = 22 - versionName = "0.1.21" + versionCode = 24 + versionName = "0.1.23" testInstrumentationRunner = "androidx.test.runner.AndroidJUnitRunner" vectorDrawables.useSupportLibrary = true diff --git a/app/src/main/java/com/kawomi/githugandroid/GitHugApp.kt b/app/src/main/java/com/kawomi/githugandroid/GitHugApp.kt index 78b9a6a..26a8dfb 100644 --- a/app/src/main/java/com/kawomi/githugandroid/GitHugApp.kt +++ b/app/src/main/java/com/kawomi/githugandroid/GitHugApp.kt @@ -38,8 +38,6 @@ import androidx.compose.material3.MaterialTheme import androidx.compose.material3.Scaffold import androidx.compose.material3.Surface import androidx.compose.material3.Text -import androidx.compose.material3.TextField -import androidx.compose.material3.TextFieldDefaults import androidx.compose.material3.TextButton import androidx.compose.material3.darkColorScheme import androidx.compose.runtime.Composable @@ -205,7 +203,7 @@ fun GitHugApp() { val persistedPaneLayout by paneLayoutStore.layoutFlow.collectAsState(initial = defaultPaneLayout()) val scope = rememberCoroutineScope() val screenScrollState = rememberScrollState() - val workspaceHeight = (configuration.screenHeightDp.dp * 0.72f).coerceAtLeast(420.dp) + val workspaceHeight = (configuration.screenHeightDp.dp * 1.05f).coerceAtLeast(560.dp) var paneLayout by remember { mutableStateOf(defaultPaneLayout()) } var currentLevelIndex by remember { mutableStateOf(0) } @@ -225,10 +223,12 @@ fun GitHugApp() { paneLayout = persistedPaneLayout } - fun updatePaneLayout(transform: (PaneLayout) -> PaneLayout) { + fun updatePaneLayout(transform: (PaneLayout) -> PaneLayout, persist: Boolean = true) { val updated = transform(paneLayout) paneLayout = updated - scope.launch { paneLayoutStore.save(updated) } + if (persist) { + scope.launch { paneLayoutStore.save(updated) } + } } fun clearCommandInput(recreateField: Boolean = false) { @@ -380,17 +380,20 @@ fun GitHugApp() { .height(workspaceHeight), paneLayout = paneLayout, onMovePane = { paneId, delta -> - updatePaneLayout { layout -> movePane(layout, paneId, delta) } + updatePaneLayout(transform = { layout -> movePane(layout, paneId, delta) }) }, onTogglePane = { paneId -> - updatePaneLayout { layout -> + updatePaneLayout(transform = { layout -> val collapsed = layout.collapsed.toMutableSet() if (!collapsed.add(paneId)) collapsed.remove(paneId) layout.copy(collapsed = collapsed) - } + }) }, onResize = { upper, lower, dragFraction -> - updatePaneLayout { layout -> resizePanes(layout, upper, lower, dragFraction) } + updatePaneLayout(transform = { layout -> resizePanes(layout, upper, lower, dragFraction) }, persist = false) + }, + onResizeFinished = { + scope.launch { paneLayoutStore.save(paneLayout) } }, levelsContent = { LevelsPane(levels, currentLevelIndex, completedLevels) { index -> loadLevel(index) } @@ -470,6 +473,7 @@ private fun PaneWorkspace( onMovePane: (PaneId, Int) -> Unit, onTogglePane: (PaneId) -> Unit, onResize: (PaneId, PaneId, Float) -> Unit, + onResizeFinished: () -> Unit, levelsContent: @Composable () -> Unit, visualContent: @Composable () -> Unit, exerciseContent: @Composable () -> Unit, @@ -523,6 +527,7 @@ private fun PaneWorkspace( val fraction = deltaPx / (heightBasis * 4f) onResize(upper, lower, fraction) }, + onDragFinished = onResizeFinished, ) } } @@ -643,6 +648,7 @@ private fun HeaderActionButton( private fun PaneDivider( enabled: Boolean, onDrag: (Float) -> Unit, + onDragFinished: () -> Unit, ) { val dragState = rememberDraggableState { delta -> if (enabled) onDrag(delta) @@ -656,6 +662,7 @@ private fun PaneDivider( state = dragState, orientation = Orientation.Vertical, enabled = enabled, + onDragStopped = { onDragFinished() }, ), contentAlignment = Alignment.Center, ) { @@ -665,6 +672,13 @@ private fun PaneDivider( .height(1.dp) .background(if (enabled) Accent.copy(alpha = 0.45f) else PanelTertiary), ) + Text( + text = "↕", + color = if (enabled) Accent else TextMuted, + modifier = Modifier.align(Alignment.CenterStart).padding(start = 28.dp), + fontSize = 12.sp, + fontWeight = FontWeight.Bold, + ) } } @@ -855,6 +869,7 @@ private fun TerminalPane( val horizontalScroll = rememberScrollState() val outputVerticalScroll = rememberScrollState() val terminalMinWidth = 640.dp + val scope = rememberCoroutineScope() LaunchedEffect(inputFieldVersion) { if (inputFieldVersion == 0) return@LaunchedEffect @@ -886,7 +901,6 @@ private fun TerminalPane( .widthIn(min = terminalMinWidth), verticalArrangement = Arrangement.spacedBy(8.dp), ) { - Text("Terminal", color = TextPrimary, fontWeight = FontWeight.Bold) Box( modifier = Modifier .weight(1f) @@ -929,36 +943,41 @@ private fun TerminalPane( fontWeight = FontWeight.Bold, ) key(inputFieldVersion) { - TextField( + BasicTextField( value = commandInput, onValueChange = onValueChange, modifier = Modifier .weight(1f) .focusRequester(focusRequester) + .bringIntoViewRequester(bringIntoViewRequester) .onFocusChanged { state -> if (state.isFocused) { keyboardController?.show() + scope.launch { + delay(150) + bringIntoViewRequester.bringIntoView() + } } }, singleLine = true, textStyle = TextStyle(color = TextPrimary, fontFamily = FontFamily.Monospace), - colors = TextFieldDefaults.colors( - focusedContainerColor = Color.Transparent, - unfocusedContainerColor = Color.Transparent, - disabledContainerColor = Color.Transparent, - cursorColor = Accent, - focusedIndicatorColor = Color.Transparent, - unfocusedIndicatorColor = Color.Transparent, - disabledIndicatorColor = Color.Transparent, - focusedTextColor = TextPrimary, - unfocusedTextColor = TextPrimary, - ), + cursorBrush = SolidColor(Accent), keyboardOptions = KeyboardOptions( autoCorrect = false, keyboardType = KeyboardType.Ascii, imeAction = ImeAction.Done, ), keyboardActions = KeyboardActions(onDone = { submitCommand() }), + decorationBox = { innerTextField -> + Box( + modifier = Modifier + .fillMaxWidth() + .padding(vertical = 8.dp), + contentAlignment = Alignment.CenterStart, + ) { + innerTextField() + } + }, ) } }