Auto-commit after successful build: update app gameplay/UI, improve build setup

Changed files:\napp/build.gradle.kts
app/src/main/java/com/kawomi/githugandroid/GitHugApp.kt
This commit is contained in:
Joe Tretter
2026-04-23 10:11:01 -05:00
parent c0ea54c784
commit 21334203c7
2 changed files with 73 additions and 36 deletions

View File

@@ -11,8 +11,8 @@ android {
applicationId = "com.kawomi.githugandroid" applicationId = "com.kawomi.githugandroid"
minSdk = 26 minSdk = 26
targetSdk = 34 targetSdk = 34
versionCode = 20 versionCode = 22
versionName = "0.1.19" versionName = "0.1.21"
testInstrumentationRunner = "androidx.test.runner.AndroidJUnitRunner" testInstrumentationRunner = "androidx.test.runner.AndroidJUnitRunner"
vectorDrawables.useSupportLibrary = true vectorDrawables.useSupportLibrary = true

View File

@@ -7,16 +7,19 @@ import androidx.compose.foundation.gestures.Orientation
import androidx.compose.foundation.gestures.draggable import androidx.compose.foundation.gestures.draggable
import androidx.compose.foundation.gestures.rememberDraggableState import androidx.compose.foundation.gestures.rememberDraggableState
import androidx.compose.foundation.horizontalScroll import androidx.compose.foundation.horizontalScroll
import androidx.compose.foundation.ExperimentalFoundationApi
import androidx.compose.foundation.layout.Arrangement import androidx.compose.foundation.layout.Arrangement
import androidx.compose.foundation.layout.Box import androidx.compose.foundation.layout.Box
import androidx.compose.foundation.layout.BoxWithConstraints import androidx.compose.foundation.layout.BoxWithConstraints
import androidx.compose.foundation.layout.Column import androidx.compose.foundation.layout.Column
import androidx.compose.foundation.layout.PaddingValues import androidx.compose.foundation.layout.PaddingValues
import androidx.compose.foundation.layout.Row import androidx.compose.foundation.layout.Row
import androidx.compose.foundation.layout.Spacer
import androidx.compose.foundation.layout.fillMaxHeight import androidx.compose.foundation.layout.fillMaxHeight
import androidx.compose.foundation.layout.fillMaxSize import androidx.compose.foundation.layout.fillMaxSize
import androidx.compose.foundation.layout.fillMaxWidth import androidx.compose.foundation.layout.fillMaxWidth
import androidx.compose.foundation.layout.height import androidx.compose.foundation.layout.height
import androidx.compose.foundation.layout.imePadding
import androidx.compose.foundation.layout.padding import androidx.compose.foundation.layout.padding
import androidx.compose.foundation.layout.size import androidx.compose.foundation.layout.size
import androidx.compose.foundation.layout.width import androidx.compose.foundation.layout.width
@@ -35,6 +38,8 @@ import androidx.compose.material3.MaterialTheme
import androidx.compose.material3.Scaffold import androidx.compose.material3.Scaffold
import androidx.compose.material3.Surface import androidx.compose.material3.Surface
import androidx.compose.material3.Text import androidx.compose.material3.Text
import androidx.compose.material3.TextField
import androidx.compose.material3.TextFieldDefaults
import androidx.compose.material3.TextButton import androidx.compose.material3.TextButton
import androidx.compose.material3.darkColorScheme import androidx.compose.material3.darkColorScheme
import androidx.compose.runtime.Composable import androidx.compose.runtime.Composable
@@ -50,6 +55,7 @@ import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier import androidx.compose.ui.Modifier
import androidx.compose.ui.focus.FocusRequester 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.graphics.Color import androidx.compose.ui.graphics.Color
import androidx.compose.ui.graphics.SolidColor import androidx.compose.ui.graphics.SolidColor
import androidx.compose.ui.platform.LocalConfiguration import androidx.compose.ui.platform.LocalConfiguration
@@ -66,6 +72,8 @@ import androidx.compose.ui.text.input.KeyboardType
import androidx.compose.ui.text.input.TextFieldValue import androidx.compose.ui.text.input.TextFieldValue
import androidx.compose.ui.unit.sp import androidx.compose.ui.unit.sp
import androidx.compose.ui.unit.dp import androidx.compose.ui.unit.dp
import androidx.compose.foundation.relocation.BringIntoViewRequester
import androidx.compose.foundation.relocation.bringIntoViewRequester
import androidx.datastore.preferences.core.PreferenceDataStoreFactory import androidx.datastore.preferences.core.PreferenceDataStoreFactory
import androidx.datastore.preferences.core.edit import androidx.datastore.preferences.core.edit
import androidx.datastore.preferences.core.emptyPreferences import androidx.datastore.preferences.core.emptyPreferences
@@ -119,10 +127,10 @@ private fun defaultPaneLayout(): PaneLayout = PaneLayout(
order = listOf(PaneId.LEVELS, PaneId.VISUAL, PaneId.EXERCISE, PaneId.TERMINAL), order = listOf(PaneId.LEVELS, PaneId.VISUAL, PaneId.EXERCISE, PaneId.TERMINAL),
collapsed = emptySet(), collapsed = emptySet(),
weights = mapOf( weights = mapOf(
PaneId.LEVELS to 1.1f, PaneId.LEVELS to 0.9f,
PaneId.VISUAL to 1.3f, PaneId.VISUAL to 1.0f,
PaneId.EXERCISE to 1.0f, PaneId.EXERCISE to 0.9f,
PaneId.TERMINAL to 1.8f, PaneId.TERMINAL to 1.4f,
), ),
) )
@@ -190,11 +198,14 @@ private class PaneLayoutStore(private val context: Context) {
fun GitHugApp() { fun GitHugApp() {
MaterialTheme(colorScheme = GitHugColorScheme) { MaterialTheme(colorScheme = GitHugColorScheme) {
val context = LocalContext.current val context = LocalContext.current
val configuration = LocalConfiguration.current
val levels = remember { sampleLevels() } val levels = remember { sampleLevels() }
val runtime = remember(context) { GitRepositoryRuntime(context.applicationContext) } val runtime = remember(context) { GitRepositoryRuntime(context.applicationContext) }
val paneLayoutStore = remember(context) { PaneLayoutStore(context.applicationContext) } val paneLayoutStore = remember(context) { PaneLayoutStore(context.applicationContext) }
val persistedPaneLayout by paneLayoutStore.layoutFlow.collectAsState(initial = defaultPaneLayout()) val persistedPaneLayout by paneLayoutStore.layoutFlow.collectAsState(initial = defaultPaneLayout())
val scope = rememberCoroutineScope() val scope = rememberCoroutineScope()
val screenScrollState = rememberScrollState()
val workspaceHeight = (configuration.screenHeightDp.dp * 0.72f).coerceAtLeast(420.dp)
var paneLayout by remember { mutableStateOf(defaultPaneLayout()) } var paneLayout by remember { mutableStateOf(defaultPaneLayout()) }
var currentLevelIndex by remember { mutableStateOf(0) } var currentLevelIndex by remember { mutableStateOf(0) }
@@ -355,13 +366,18 @@ fun GitHugApp() {
) { ) {
Column( Column(
modifier = Modifier modifier = Modifier
.fillMaxSize() .fillMaxWidth()
.background(AppBackground) .background(AppBackground)
.padding(12.dp), .verticalScroll(screenScrollState)
verticalArrangement = Arrangement.spacedBy(12.dp), .imePadding()
.padding(horizontal = 10.dp, vertical = 8.dp),
verticalArrangement = Arrangement.spacedBy(8.dp),
) { ) {
FixedHeader() FixedHeader()
PaneWorkspace( PaneWorkspace(
modifier = Modifier
.fillMaxWidth()
.height(workspaceHeight),
paneLayout = paneLayout, paneLayout = paneLayout,
onMovePane = { paneId, delta -> onMovePane = { paneId, delta ->
updatePaneLayout { layout -> movePane(layout, paneId, delta) } updatePaneLayout { layout -> movePane(layout, paneId, delta) }
@@ -417,6 +433,7 @@ fun GitHugApp() {
) )
}, },
) )
Spacer(modifier = Modifier.height(220.dp))
} }
} }
} }
@@ -427,30 +444,28 @@ fun GitHugApp() {
private fun FixedHeader() { private fun FixedHeader() {
Card( Card(
colors = CardDefaults.cardColors(containerColor = PanelPrimary), colors = CardDefaults.cardColors(containerColor = PanelPrimary),
shape = RoundedCornerShape(18.dp), shape = RoundedCornerShape(14.dp),
) { ) {
Row( Row(
modifier = Modifier modifier = Modifier
.fillMaxWidth() .fillMaxWidth()
.padding(horizontal = 16.dp, vertical = 14.dp), .padding(horizontal = 12.dp, vertical = 10.dp),
verticalAlignment = Alignment.CenterVertically, verticalAlignment = Alignment.CenterVertically,
horizontalArrangement = Arrangement.spacedBy(12.dp), horizontalArrangement = Arrangement.spacedBy(10.dp),
) { ) {
Image( Image(
painter = painterResource(id = R.drawable.githug_android_logo), painter = painterResource(id = R.drawable.githug_android_logo),
contentDescription = "GitHug Android logo", contentDescription = "GitHug Android logo",
modifier = Modifier.size(40.dp), modifier = Modifier.size(28.dp),
) )
Column(verticalArrangement = Arrangement.spacedBy(2.dp)) { Text("GitHug Android", style = MaterialTheme.typography.titleLarge, color = TextPrimary)
Text("GitHug Android", style = MaterialTheme.typography.headlineSmall, color = TextPrimary)
Text("Customizable panes for Git learning on phone and tablet.", color = TextSecondary)
}
} }
} }
} }
@Composable @Composable
private fun PaneWorkspace( private fun PaneWorkspace(
modifier: Modifier = Modifier,
paneLayout: PaneLayout, paneLayout: PaneLayout,
onMovePane: (PaneId, Int) -> Unit, onMovePane: (PaneId, Int) -> Unit,
onTogglePane: (PaneId) -> Unit, onTogglePane: (PaneId) -> Unit,
@@ -460,7 +475,7 @@ private fun PaneWorkspace(
exerciseContent: @Composable () -> Unit, exerciseContent: @Composable () -> Unit,
terminalContent: @Composable () -> Unit, terminalContent: @Composable () -> Unit,
) { ) {
BoxWithConstraints(modifier = Modifier.fillMaxSize()) { BoxWithConstraints(modifier = modifier) {
val heightBasis = maxHeight.value.takeIf { it > 0f } ?: 1f val heightBasis = maxHeight.value.takeIf { it > 0f } ?: 1f
Column( Column(
@@ -592,7 +607,7 @@ private fun PaneHeader(
modifier = Modifier modifier = Modifier
.fillMaxWidth() .fillMaxWidth()
.background(if (collapsed) PanelTertiary else PanelPrimary) .background(if (collapsed) PanelTertiary else PanelPrimary)
.padding(horizontal = 12.dp, vertical = 10.dp), .padding(horizontal = 10.dp, vertical = 6.dp),
verticalAlignment = Alignment.CenterVertically, verticalAlignment = Alignment.CenterVertically,
horizontalArrangement = Arrangement.SpaceBetween, horizontalArrangement = Arrangement.SpaceBetween,
) { ) {
@@ -614,13 +629,13 @@ private fun HeaderActionButton(
TextButton( TextButton(
onClick = onClick, onClick = onClick,
enabled = enabled, enabled = enabled,
contentPadding = PaddingValues(horizontal = 8.dp, vertical = 0.dp), contentPadding = PaddingValues(horizontal = 6.dp, vertical = 0.dp),
colors = ButtonDefaults.textButtonColors( colors = ButtonDefaults.textButtonColors(
contentColor = Accent, contentColor = Accent,
disabledContentColor = TextMuted, disabledContentColor = TextMuted,
), ),
) { ) {
Text(label, fontSize = 12.sp) Text(label, fontSize = 11.sp)
} }
} }
@@ -636,7 +651,7 @@ private fun PaneDivider(
Box( Box(
modifier = Modifier modifier = Modifier
.fillMaxWidth() .fillMaxWidth()
.height(12.dp) .height(6.dp)
.draggable( .draggable(
state = dragState, state = dragState,
orientation = Orientation.Vertical, orientation = Orientation.Vertical,
@@ -647,7 +662,7 @@ private fun PaneDivider(
Box( Box(
modifier = Modifier modifier = Modifier
.fillMaxWidth() .fillMaxWidth()
.height(2.dp) .height(1.dp)
.background(if (enabled) Accent.copy(alpha = 0.45f) else PanelTertiary), .background(if (enabled) Accent.copy(alpha = 0.45f) else PanelTertiary),
) )
} }
@@ -671,7 +686,7 @@ private fun LevelsPane(
colors = CardDefaults.cardColors( colors = CardDefaults.cardColors(
containerColor = if (index == currentLevelIndex) PanelTertiary else PanelPrimary, containerColor = if (index == currentLevelIndex) PanelTertiary else PanelPrimary,
), ),
shape = RoundedCornerShape(12.dp), shape = RoundedCornerShape(10.dp),
) { ) {
TextButton( TextButton(
onClick = { onSelect(index) }, onClick = { onSelect(index) },
@@ -800,13 +815,13 @@ private fun InfoPaneCard(
Card( Card(
modifier = modifier.fillMaxWidth(), modifier = modifier.fillMaxWidth(),
colors = CardDefaults.cardColors(containerColor = PanelPrimary), colors = CardDefaults.cardColors(containerColor = PanelPrimary),
shape = RoundedCornerShape(14.dp), shape = RoundedCornerShape(10.dp),
) { ) {
Column( Column(
modifier = Modifier modifier = Modifier
.fillMaxSize() .fillMaxSize()
.padding(12.dp), .padding(8.dp),
verticalArrangement = Arrangement.spacedBy(8.dp), verticalArrangement = Arrangement.spacedBy(4.dp),
) { ) {
Text(title, color = TextPrimary, fontWeight = FontWeight.Bold) Text(title, color = TextPrimary, fontWeight = FontWeight.Bold)
Text( Text(
@@ -818,6 +833,7 @@ private fun InfoPaneCard(
} }
} }
@OptIn(ExperimentalFoundationApi::class)
@Composable @Composable
private fun TerminalPane( private fun TerminalPane(
output: List<String>, output: List<String>,
@@ -833,6 +849,7 @@ private fun TerminalPane(
onHistoryDown: () -> Unit, onHistoryDown: () -> Unit,
) { ) {
val focusRequester = remember { FocusRequester() } val focusRequester = remember { FocusRequester() }
val bringIntoViewRequester = remember { BringIntoViewRequester() }
val focusManager = LocalFocusManager.current val focusManager = LocalFocusManager.current
val keyboardController = LocalSoftwareKeyboardController.current val keyboardController = LocalSoftwareKeyboardController.current
val horizontalScroll = rememberScrollState() val horizontalScroll = rememberScrollState()
@@ -846,6 +863,10 @@ private fun TerminalPane(
keyboardController?.show() keyboardController?.show()
} }
LaunchedEffect(output.size) {
outputVerticalScroll.animateScrollTo(outputVerticalScroll.maxValue)
}
fun submitCommand() { fun submitCommand() {
focusManager.clearFocus(force = true) focusManager.clearFocus(force = true)
keyboardController?.hide() keyboardController?.hide()
@@ -856,7 +877,7 @@ private fun TerminalPane(
modifier = Modifier modifier = Modifier
.fillMaxSize() .fillMaxSize()
.background(TerminalBackground, RoundedCornerShape(12.dp)) .background(TerminalBackground, RoundedCornerShape(12.dp))
.padding(12.dp) .padding(8.dp)
.horizontalScroll(horizontalScroll), .horizontalScroll(horizontalScroll),
) { ) {
Column( Column(
@@ -871,7 +892,7 @@ private fun TerminalPane(
.weight(1f) .weight(1f)
.fillMaxWidth() .fillMaxWidth()
.background(PanelPrimary, RoundedCornerShape(10.dp)) .background(PanelPrimary, RoundedCornerShape(10.dp))
.padding(12.dp) .padding(8.dp)
.verticalScroll(outputVerticalScroll), .verticalScroll(outputVerticalScroll),
) { ) {
Column(verticalArrangement = Arrangement.spacedBy(4.dp)) { Column(verticalArrangement = Arrangement.spacedBy(4.dp)) {
@@ -894,9 +915,10 @@ private fun TerminalPane(
) )
Row( Row(
modifier = Modifier modifier = Modifier
.bringIntoViewRequester(bringIntoViewRequester)
.fillMaxWidth() .fillMaxWidth()
.background(PanelPrimary, RoundedCornerShape(8.dp)) .background(PanelPrimary, RoundedCornerShape(8.dp))
.padding(horizontal = 12.dp, vertical = 10.dp), .padding(horizontal = 8.dp, vertical = 4.dp),
horizontalArrangement = Arrangement.spacedBy(8.dp), horizontalArrangement = Arrangement.spacedBy(8.dp),
verticalAlignment = Alignment.CenterVertically, verticalAlignment = Alignment.CenterVertically,
) { ) {
@@ -907,15 +929,30 @@ private fun TerminalPane(
fontWeight = FontWeight.Bold, fontWeight = FontWeight.Bold,
) )
key(inputFieldVersion) { key(inputFieldVersion) {
BasicTextField( TextField(
value = commandInput, value = commandInput,
onValueChange = onValueChange, onValueChange = onValueChange,
modifier = Modifier modifier = Modifier
.weight(1f) .weight(1f)
.focusRequester(focusRequester), .focusRequester(focusRequester)
.onFocusChanged { state ->
if (state.isFocused) {
keyboardController?.show()
}
},
singleLine = true, singleLine = true,
textStyle = TextStyle(color = TextPrimary, fontFamily = FontFamily.Monospace), textStyle = TextStyle(color = TextPrimary, fontFamily = FontFamily.Monospace),
cursorBrush = SolidColor(Accent), 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,
),
keyboardOptions = KeyboardOptions( keyboardOptions = KeyboardOptions(
autoCorrect = false, autoCorrect = false,
keyboardType = KeyboardType.Ascii, keyboardType = KeyboardType.Ascii,
@@ -960,8 +997,8 @@ private fun TerminalKeyButton(
) { ) {
Button( Button(
onClick = onClick, onClick = onClick,
modifier = modifier.height(34.dp), modifier = modifier.height(28.dp),
contentPadding = PaddingValues(horizontal = 10.dp, vertical = 4.dp), contentPadding = PaddingValues(horizontal = 8.dp, vertical = 2.dp),
colors = ButtonDefaults.buttonColors( colors = ButtonDefaults.buttonColors(
containerColor = PanelSecondary, containerColor = PanelSecondary,
contentColor = TextPrimary, contentColor = TextPrimary,
@@ -971,7 +1008,7 @@ private fun TerminalKeyButton(
text = label, text = label,
fontFamily = fontFamily, fontFamily = fontFamily,
fontWeight = FontWeight.Bold, fontWeight = FontWeight.Bold,
fontSize = 13.sp, fontSize = 11.sp,
) )
} }
} }