From 516765364014aaca15c9c88544f5f324141b1485 Mon Sep 17 00:00:00 2001 From: Martin Date: Wed, 12 Nov 2025 13:23:58 +0100 Subject: [PATCH 1/3] fixed issue with StringParam not being serializable --- gradle/libs.versions.toml | 2 +- .../kotlin/de/tillhub/inputengine/domain/StringParam.kt | 2 ++ 2 files changed, 3 insertions(+), 1 deletion(-) diff --git a/gradle/libs.versions.toml b/gradle/libs.versions.toml index 96158f0..5f9c057 100644 --- a/gradle/libs.versions.toml +++ b/gradle/libs.versions.toml @@ -1,5 +1,5 @@ [versions] -input-engine = "2.1.0" +input-engine = "2.1.1" activityKtx = "1.10.1" agp = "8.11.1" androidx-activity = "1.10.1" diff --git a/input-engine/src/commonMain/kotlin/de/tillhub/inputengine/domain/StringParam.kt b/input-engine/src/commonMain/kotlin/de/tillhub/inputengine/domain/StringParam.kt index cb1ff6f..1b7bb07 100644 --- a/input-engine/src/commonMain/kotlin/de/tillhub/inputengine/domain/StringParam.kt +++ b/input-engine/src/commonMain/kotlin/de/tillhub/inputengine/domain/StringParam.kt @@ -4,9 +4,11 @@ import kotlinx.serialization.Serializable @Serializable sealed class StringParam { + @Serializable data class Enable( val value: String, ) : StringParam() + @Serializable data object Disable : StringParam() } From 7ed8075fb2336cc66da8ddb1501583453ee0be02 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=C4=90or=C4=91e=20Hrnjez?= Date: Tue, 10 Feb 2026 09:42:00 +0100 Subject: [PATCH 2/3] Fix and optimization for A920 screen --- .github/workflows/develop.yml | 25 +-- CLAUDE.md | 185 ++++++++++++++++++ gradle/libs.versions.toml | 2 +- .../ui/components/AmountInputPreview.kt | 6 +- .../ui/components/PercentageInputPreview.kt | 6 +- .../ui/components/QuantityInputPreview.kt | 4 +- .../ui/screens/AmountInputScreen.kt | 2 +- .../ui/screens/PercentageInputScreen.kt | 2 +- .../inputengine/ui/screens/PinInputScreen.kt | 6 +- .../ui/screens/QuantityInputScreen.kt | 2 +- 10 files changed, 199 insertions(+), 41 deletions(-) create mode 100644 CLAUDE.md diff --git a/.github/workflows/develop.yml b/.github/workflows/develop.yml index 4df34e3..2237e1d 100644 --- a/.github/workflows/develop.yml +++ b/.github/workflows/develop.yml @@ -36,34 +36,13 @@ jobs: - name: Gradle cache uses: gradle/actions/setup-gradle@v3 - - name: AVD cache - uses: actions/cache@v4 - id: avd-cache - with: - path: | - ~/.android/avd/* - ~/.android/adb* - key: avd-30 - - - name: Create AVD and generate snapshot for caching - if: steps.avd-cache.outputs.cache-hit != 'true' - uses: ReactiveCircus/android-emulator-runner@v2.33.0 - with: - api-level: 30 - target: google_atd - arch: x86 - force-avd-creation: false - emulator-options: -no-window -gpu swiftshader_indirect -noaudio -no-boot-anim -camera-back none - disable-animations: false - script: echo "Generated AVD snapshot for caching." - - name: Run Android tests - uses: ReactiveCircus/android-emulator-runner@v2.33.0 + uses: ReactiveCircus/android-emulator-runner@v2.35.0 with: api-level: 30 target: google_atd arch: x86 force-avd-creation: false - emulator-options: -no-snapshot-save -no-window -gpu swiftshader_indirect -noaudio -no-boot-anim -camera-back none + emulator-options: -no-window -gpu swiftshader_indirect -noaudio -no-boot-anim -camera-back none disable-animations: true script: ./gradlew :input-engine:connectedAndroidTest diff --git a/CLAUDE.md b/CLAUDE.md new file mode 100644 index 0000000..c2ffd86 --- /dev/null +++ b/CLAUDE.md @@ -0,0 +1,185 @@ +# CLAUDE.md + +This file provides guidance to Claude Code (claude.ai/code) when working with code in this repository. + +## Project Overview + +Input Engine is a Kotlin Multiplatform UI Library that provides customizable input screens for money amounts, percentages, quantities, and PIN entry. It supports both Android and iOS platforms using shared Compose Multiplatform UI code. + +## Build Commands + +### Building and Testing + +```bash +# Build the entire project +./gradlew build + +# Build only the input-engine library +./gradlew :input-engine:build + +# Run unit tests (Android) +./gradlew testDebug + +# Run Android instrumented tests (requires emulator or device) +./gradlew :input-engine:connectedAndroidTest + +# Build sample app +./gradlew :sample:assemble +``` + +### Code Quality + +```bash +# Check code formatting (ktlint) +./gradlew spotlessCheck + +# Auto-fix formatting issues +./gradlew spotlessApply + +# Run Android lint +./gradlew lint +./gradlew lintFix # Apply safe suggestions +``` + +### iOS Development + +```bash +# Build XCFramework for iOS integration +./gradlew :input-engine:assembleXCFramework +``` + +Note: iOS builds require Xcode and macOS. The XCFramework will be generated in `input-engine/build/`. + +### Publishing + +```bash +# Publish to Maven Central (requires credentials) +./gradlew publishAndReleaseToMavenCentral --no-configuration-cache +``` + +## Architecture + +### Multiplatform Structure + +The codebase follows standard Kotlin Multiplatform conventions with these source sets: + +- **`commonMain/`** - Shared business logic, UI components (Compose Multiplatform), and contracts +- **`androidMain/`** - Android-specific implementations (Activities, Activity Result Contracts) +- **`iosMain/`** - iOS-specific implementations (Presenters, UIViewController bridges) +- **`commonTest/`, `androidUnitTest/`, `iosTest/`** - Platform-specific tests + +### Key Package Structure + +- **`contract/`** - Platform-agnostic input contracts with `expect`/`actual` implementations + - Common interfaces define the contract (e.g., `AmountInputContract`) + - Android uses Activity Result API + - iOS uses Presenter pattern with `ComposeUIViewController` + - Legacy Android contracts exist in `androidMain/contract/legacy/` for backward compatibility + +- **`data/`** - Data models and I/O types + - `MoneyIO`, `PercentIO`, `QuantityIO` - Serializable data transfer objects + - `MoneyParam`, `PercentageParam`, `QuantityParam` - Configuration parameters (Enable/Disable sealed classes) + - `CurrencyIO` - Currency representation + +- **`domain/`** - Domain logic and utilities + - `NumpadKey`, `Digit` - Input handling abstractions + - `StringParam` - Sealed class for optional string parameters + - `helper/` - Business logic helpers + +- **`formatting/`** - Platform-specific number formatters + - Common interfaces with `expect`/`actual` implementations + - Uses native platform formatting (Android: `NumberFormat`, iOS: `NSNumberFormatter`) + +- **`ui/`** - Compose Multiplatform UI layer + - `screens/` - Full-screen composables (`AmountInputScreen`, `QuantityInputScreen`, etc.) + - `components/` - Reusable UI components (`NumberButton`, `Toolbar`, etc.) + - ViewModels in `commonMain` using `androidx.lifecycle` + - Android Activities bridge to ViewModels + - iOS Presenters use `ComposeUIViewController` to display screens + +- **`theme/`** - Compose theming and styling + +### Contract Pattern + +The library uses a contract-based API where each input type has: + +1. **Request** - Input parameters (e.g., `AmountInputRequest`) +2. **Result** - Sealed class with `Success` or `Canceled` (e.g., `AmountInputResult`) +3. **Contract** - Platform-specific launcher interface + +Example flow: +- Android: Uses `rememberLauncherForActivityResult` to launch Activities +- iOS: Uses `AmountInputPresenter` to present `ComposeUIViewController` +- Both platforms share the same Compose UI screens and ViewModels + +### ViewModel Factory Pattern + +ViewModels are created using factory pattern with `CreationExtras`: +- Request data and formatters are passed via `CreationExtras` +- iOS uses `MutableCreationExtras` in Presenters +- Android Activities retrieve extras from Intent + +## Configuration + +### Build Configuration + +- **Application ID**: `de.tillhub.inputengine` +- **Min SDK**: 24 (Android 7.0) +- **Compile SDK**: 35 +- **Java Version**: 17 +- **iOS Framework Name**: `input-engineKit` +- **Maven Coordinates**: `io.github.tillhub:input-engine` + +### Gradle Plugins + +- Kotlin Multiplatform +- Android Library +- Compose Multiplatform & Compose Compiler +- Kotlinx Serialization +- Mokkery (for mocking in tests) +- Spotless (code formatting with ktlint) +- Maven Publishing + +## Testing + +### Test Exclusions + +UI tests are excluded from unit test runs (configured in `input-engine/build.gradle.kts`): +- `**/inputengine/ui/components/**` +- `**/inputengine/ui/screens/**` + +These are run separately as instrumented tests on Android or as iOS UI tests. + +### Running Specific Tests + +```bash +# Run tests for a specific formatter +./gradlew testDebug --tests "*MoneyFormatterTest" + +# Run all formatting tests +./gradlew testDebug --tests "*.formatting.*" +``` + +## CI/CD Workflows + +- **PR Checks** (`pr-checks.yml`): Runs `spotlessCheck` and `testDebug` on pull requests +- **Develop Branch** (`develop.yml`): Runs Android instrumented tests on emulator +- **Master Branch** (`publish.yml`): Publishes to Maven Central (macOS runner required for iOS compilation) + +## Important Notes + +### Serialization + +All request/result classes use `kotlinx.serialization` with `@Serializable` annotations. Android passes serialized JSON through Intent extras; iOS passes objects directly through ViewModels. + +### Expect/Actual Pattern + +Platform-specific implementations are marked with `expect` (common) and `actual` (platform-specific): +- Contracts (`rememberAmountInputLauncher`, etc.) +- Formatters (`MoneyFormatterImpl`, `DecimalFormatter`, etc.) + +When modifying these, ensure both Android and iOS implementations are updated. + +### Legacy Support + +Android maintains legacy Activity Result Contracts in `androidMain/contract/legacy/` for apps that haven't migrated to the Compose-based API. Do not remove these without coordination. diff --git a/gradle/libs.versions.toml b/gradle/libs.versions.toml index 5f9c057..c985c63 100644 --- a/gradle/libs.versions.toml +++ b/gradle/libs.versions.toml @@ -1,5 +1,5 @@ [versions] -input-engine = "2.1.1" +input-engine = "2.1.2" activityKtx = "1.10.1" agp = "8.11.1" androidx-activity = "1.10.1" diff --git a/input-engine/src/commonMain/kotlin/de/tillhub/inputengine/ui/components/AmountInputPreview.kt b/input-engine/src/commonMain/kotlin/de/tillhub/inputengine/ui/components/AmountInputPreview.kt index 59b9828..bb8a2d3 100644 --- a/input-engine/src/commonMain/kotlin/de/tillhub/inputengine/ui/components/AmountInputPreview.kt +++ b/input-engine/src/commonMain/kotlin/de/tillhub/inputengine/ui/components/AmountInputPreview.kt @@ -3,7 +3,6 @@ package de.tillhub.inputengine.ui.components import androidx.compose.foundation.layout.Arrangement import androidx.compose.foundation.layout.Column import androidx.compose.foundation.layout.fillMaxWidth -import androidx.compose.foundation.layout.padding import androidx.compose.material3.MaterialTheme import androidx.compose.material3.Text import androidx.compose.runtime.Composable @@ -11,7 +10,6 @@ import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier import androidx.compose.ui.semantics.contentDescription import androidx.compose.ui.semantics.semantics -import androidx.compose.ui.unit.dp import de.tillhub.inputengine.domain.StringParam import de.tillhub.inputengine.resources.Res import de.tillhub.inputengine.resources.max_value @@ -33,7 +31,7 @@ internal fun AmountInputPreview( verticalArrangement = Arrangement.Center, ) { Text( - modifier = Modifier.padding(16.dp).semantics { contentDescription = "Current amount" }, + modifier = Modifier.semantics { contentDescription = "Current amount" }, style = MaterialTheme.typography.displayLarge, maxLines = 1, text = amount.text, @@ -47,7 +45,7 @@ internal fun AmountInputPreview( if (amountMin is StringParam.Enable) { Text( - modifier = Modifier.padding(bottom = 8.dp).semantics { contentDescription = "Min allowed amount" }, + modifier = Modifier.semantics { contentDescription = "Min allowed amount" }, style = MaterialTheme.typography.labelSmall, maxLines = 1, text = stringResource(Res.string.min_value, amountMin.value), diff --git a/input-engine/src/commonMain/kotlin/de/tillhub/inputengine/ui/components/PercentageInputPreview.kt b/input-engine/src/commonMain/kotlin/de/tillhub/inputengine/ui/components/PercentageInputPreview.kt index 2056f34..527037f 100644 --- a/input-engine/src/commonMain/kotlin/de/tillhub/inputengine/ui/components/PercentageInputPreview.kt +++ b/input-engine/src/commonMain/kotlin/de/tillhub/inputengine/ui/components/PercentageInputPreview.kt @@ -3,7 +3,6 @@ package de.tillhub.inputengine.ui.components import androidx.compose.foundation.layout.Arrangement import androidx.compose.foundation.layout.Column import androidx.compose.foundation.layout.fillMaxWidth -import androidx.compose.foundation.layout.padding import androidx.compose.material3.MaterialTheme import androidx.compose.material3.Text import androidx.compose.runtime.Composable @@ -11,7 +10,6 @@ import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier import androidx.compose.ui.semantics.contentDescription import androidx.compose.ui.semantics.semantics -import androidx.compose.ui.unit.dp import de.tillhub.inputengine.domain.StringParam import de.tillhub.inputengine.resources.Res import de.tillhub.inputengine.resources.max_value @@ -31,7 +29,7 @@ fun PercentageInputPreview( verticalArrangement = Arrangement.Center, ) { Text( - modifier = Modifier.padding(16.dp).semantics { contentDescription = "Current percentage" }, + modifier = Modifier.semantics { contentDescription = "Current percentage" }, style = MaterialTheme.typography.displayLarge, maxLines = 1, text = percentText, @@ -40,7 +38,7 @@ fun PercentageInputPreview( if (percentageMin is StringParam.Enable) { Text( - modifier = Modifier.padding(bottom = 8.dp).semantics { contentDescription = "Min allowed percentage" }, + modifier = Modifier.semantics { contentDescription = "Min allowed percentage" }, style = MaterialTheme.typography.labelSmall, maxLines = 1, text = stringResource(Res.string.min_value, percentageMin.value), diff --git a/input-engine/src/commonMain/kotlin/de/tillhub/inputengine/ui/components/QuantityInputPreview.kt b/input-engine/src/commonMain/kotlin/de/tillhub/inputengine/ui/components/QuantityInputPreview.kt index b358400..78b58d0 100644 --- a/input-engine/src/commonMain/kotlin/de/tillhub/inputengine/ui/components/QuantityInputPreview.kt +++ b/input-engine/src/commonMain/kotlin/de/tillhub/inputengine/ui/components/QuantityInputPreview.kt @@ -64,7 +64,7 @@ internal fun QuantityInputPreview( } Text( text = quantity.text, - modifier = Modifier.padding(16.dp).semantics { contentDescription = "Current quantity" }, + modifier = Modifier.padding(horizontal = 16.dp).semantics { contentDescription = "Current quantity" }, style = MaterialTheme.typography.displayLarge, color = if (quantity.isHint) { @@ -92,7 +92,7 @@ internal fun QuantityInputPreview( if (minQuantity is StringParam.Enable) { Text( - modifier = Modifier.padding(bottom = 8.dp).semantics { contentDescription = "Min allowed quantity" }, + modifier = Modifier.semantics { contentDescription = "Min allowed quantity" }, text = stringResource(Res.string.min_value, minQuantity.value), style = MaterialTheme.typography.labelSmall, color = MaterialTheme.colorScheme.onSecondary, diff --git a/input-engine/src/commonMain/kotlin/de/tillhub/inputengine/ui/screens/AmountInputScreen.kt b/input-engine/src/commonMain/kotlin/de/tillhub/inputengine/ui/screens/AmountInputScreen.kt index 8cb6c5b..162ce06 100644 --- a/input-engine/src/commonMain/kotlin/de/tillhub/inputengine/ui/screens/AmountInputScreen.kt +++ b/input-engine/src/commonMain/kotlin/de/tillhub/inputengine/ui/screens/AmountInputScreen.kt @@ -75,7 +75,7 @@ internal fun AmountInputScreen( amountMax = amountMax, ) NumberKeyboard( - modifier = Modifier.padding(vertical = 24.dp), + modifier = Modifier.padding(vertical = 8.dp), onClick = viewModel::input, showNegative = viewModel.amountInputMode == AmountInputMode.BOTH, ) diff --git a/input-engine/src/commonMain/kotlin/de/tillhub/inputengine/ui/screens/PercentageInputScreen.kt b/input-engine/src/commonMain/kotlin/de/tillhub/inputengine/ui/screens/PercentageInputScreen.kt index 2eab322..395a4fc 100644 --- a/input-engine/src/commonMain/kotlin/de/tillhub/inputengine/ui/screens/PercentageInputScreen.kt +++ b/input-engine/src/commonMain/kotlin/de/tillhub/inputengine/ui/screens/PercentageInputScreen.kt @@ -72,7 +72,7 @@ internal fun PercentageInputScreen( percentageMax = viewModel.maxStringParam, ) NumberKeyboard( - modifier = Modifier.padding(vertical = 24.dp), + modifier = Modifier.padding(vertical = 8.dp), onClick = viewModel::input, showDecimalSeparator = viewModel.allowDecimal, ) diff --git a/input-engine/src/commonMain/kotlin/de/tillhub/inputengine/ui/screens/PinInputScreen.kt b/input-engine/src/commonMain/kotlin/de/tillhub/inputengine/ui/screens/PinInputScreen.kt index d82a604..148cf6d 100644 --- a/input-engine/src/commonMain/kotlin/de/tillhub/inputengine/ui/screens/PinInputScreen.kt +++ b/input-engine/src/commonMain/kotlin/de/tillhub/inputengine/ui/screens/PinInputScreen.kt @@ -3,10 +3,8 @@ package de.tillhub.inputengine.ui.screens import androidx.compose.foundation.layout.Arrangement import androidx.compose.foundation.layout.Box import androidx.compose.foundation.layout.Column -import androidx.compose.foundation.layout.Spacer import androidx.compose.foundation.layout.fillMaxHeight import androidx.compose.foundation.layout.fillMaxSize -import androidx.compose.foundation.layout.height import androidx.compose.foundation.layout.padding import androidx.compose.material3.Scaffold import androidx.compose.material3.SnackbarHost @@ -112,10 +110,10 @@ internal fun PinInputScreen( onOverride = { onResult(PinInputResult.Success(viewModel.responseExtras)) }, ) NumberKeyboard( - modifier = Modifier.padding(vertical = 24.dp), + modifier = Modifier.padding(top = 24.dp), onClick = viewModel::input, ) - Spacer(modifier = Modifier.height(64.dp)) +// Spacer(modifier = Modifier.height(64.dp)) } } } diff --git a/input-engine/src/commonMain/kotlin/de/tillhub/inputengine/ui/screens/QuantityInputScreen.kt b/input-engine/src/commonMain/kotlin/de/tillhub/inputengine/ui/screens/QuantityInputScreen.kt index b6bec9d..8fb7f0e 100644 --- a/input-engine/src/commonMain/kotlin/de/tillhub/inputengine/ui/screens/QuantityInputScreen.kt +++ b/input-engine/src/commonMain/kotlin/de/tillhub/inputengine/ui/screens/QuantityInputScreen.kt @@ -74,7 +74,7 @@ internal fun QuantityInputScreen( ) NumberKeyboard( - modifier = Modifier.padding(vertical = 24.dp), + modifier = Modifier.padding(vertical = 8.dp), showDecimalSeparator = viewModel.allowDecimal, showNegative = viewModel.allowNegative, onClick = viewModel::processKey, From 18fee4f09f5b23b8e6fa08ecf4073767de8cc391 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=C4=90or=C4=91e=20Hrnjez?= Date: Tue, 10 Feb 2026 11:05:22 +0100 Subject: [PATCH 3/3] Removed commented code --- .../kotlin/de/tillhub/inputengine/ui/screens/PinInputScreen.kt | 1 - 1 file changed, 1 deletion(-) diff --git a/input-engine/src/commonMain/kotlin/de/tillhub/inputengine/ui/screens/PinInputScreen.kt b/input-engine/src/commonMain/kotlin/de/tillhub/inputengine/ui/screens/PinInputScreen.kt index 148cf6d..f1097b1 100644 --- a/input-engine/src/commonMain/kotlin/de/tillhub/inputengine/ui/screens/PinInputScreen.kt +++ b/input-engine/src/commonMain/kotlin/de/tillhub/inputengine/ui/screens/PinInputScreen.kt @@ -113,7 +113,6 @@ internal fun PinInputScreen( modifier = Modifier.padding(top = 24.dp), onClick = viewModel::input, ) -// Spacer(modifier = Modifier.height(64.dp)) } } }