From a2e96af1af6563637c2a429be6de76333aa989e3 Mon Sep 17 00:00:00 2001 From: pjay Date: Mon, 20 Apr 2026 16:04:20 +0700 Subject: [PATCH] WIP: rename sample app to Folio (#20) * refactor: rename examples/sample-app to examples/folio Directory-level rename and path references in Go tests, bundle-check, top-level README, and getting-started docs. Package declarations, Gradle config, iOS bundle IDs, and class names follow in later commits. * refactor(folio): rename Kotlin package dev.uatu.sample to app.folio Moves source dirs and sqldelight schema from dev/uatu/sample to app/folio, updates package declarations and imports, and switches Android namespace/applicationId, iOS binaryOption bundleId, and sqldelight database packageName to the new identifier. * refactor(folio): rename SampleApplication to FolioApplication Android manifest now points at .FolioApplication with label 'Folio' instead of 'Uatu Sample'. * refactor(folio): set iOS bundle id and display name to Folio bundleIdPrefix + PRODUCT_BUNDLE_IDENTIFIER -> app.folio. CFBundleName + CFBundleDisplayName -> 'Folio'. * refactor(folio): update demo email to demo@folio.app * refactor(folio): point justfile at app.folio bundle id Updates xcrun simctl launch target, uatu test --bundle-id, and the build/uninstall comments to reference folio instead of sample. * test: update fixture package ids to app.folio Sidecar activity-resolver test and verifier spec-integration XML fixtures referenced the old dev.uatu.sample Android package. Updates them to match the folio app's real package id so the tests stay representative of what the CLI sees on-device. * test(verifier): rename SampleApp identifiers to Folio Renames TestSampleAppSpec* functions, bundleSampleAppSpec helper, and sampleAppHierarchyXML const (now loginHierarchyXML for consistency with the other per-screen fixtures). Updates trailing sample-app mentions in comments and assertion messages. * refactor(folio): rename Gradle/npm/wasm project identifiers to folio settings.gradle.kts rootProject.name, package.json + package-lock.json name, and the WasmJS index.html all still read 'uatu-sample' / 'Uatu Sample'. Realigns them with the Folio brand. * docs(folio): rewrite README title + getting-started bundle id examples/folio/README.md is now titled 'Folio' with the Kotlin source paths corrected to app/folio. Getting-started example uses --bundle-id app.folio. Harness launch message is now generic ('app under test') since uatu-sample-harness is not specific to folio. * chore(folio): drop trailing 'sample' reference in gradle.properties * refactor(folio): rename LoginPage composable to LoginScreen Align with KMP/Android industry convention (NowInAndroid, Cash App, JetBrains samples use Screen, not Page). * refactor(folio): rename HomePage composable to HomeScreen * refactor(folio): rename AddAccountPage composable to AddAccountScreen * refactor(folio): rename LedgerPage composable to LedgerScreen * refactor(folio): rename AddTransactionPage composable to AddTransactionScreen * refactor(folio): split Models.kt into app.folio.data package Account, Transaction (with TxnType), and Session move into their own files under app.folio.data, matching NowInAndroid-style per-type organization. * refactor(folio): move data layer into app.folio.data package Repository, LedgerStore (expect + interface), SqlLedgerStore, WebLedgerStore, DriverFactory (expect + actuals), AndroidLedgerContext, and Snapshot move into app.folio.data. Update all consumer imports. * refactor(folio): move Navigation into app.folio.navigation package Split the former Navigation.kt into Route.kt (sealed interface) and Navigator.kt (singleton). Update consumer imports across screens, App.kt, and FolioApplication. * refactor(folio): move Platform and Format into app.folio.platform Both files carry expect declarations (Platform object, formatDate); grouping them into a dedicated platform package makes the KMP seam obvious and mirrors the structure used by JetBrains samples. * refactor(folio): move login into feature/auth package Create app.folio.feature.auth with LoginScreen + LoginUiState. Inline the former Auth.kt (DEMO_EMAIL, DEMO_PASSWORD, checkCredentials) into LoginScreen since it is the sole caller. * refactor(folio): move HomeScreen into feature/home package * refactor(folio): move account creation into feature/account package AddAccountScreen gets its own AddAccountUiState colocated with the screen, replacing the shared UiState.addAccountError. * refactor(folio): move ledger screens into feature/ledger package LedgerScreen and AddTransactionScreen move into app.folio.feature.ledger with AddTransactionUiState (txnError, txnFormType) colocated. The former catch-all UiState.kt is removed now that each screen owns its state alongside its UI. * refactor(folio): split Theme.kt; move theme and icons to subpackages Theme split into Theme.kt (tokens, layout dims, LedgerTheme) and Type.kt (typography) under app.folio.ui.theme. Icons moves to app.folio.ui.icon. Update every consumer's imports to match. * refactor(folio): split ui components into per-file under ui/component Former Widgets.kt and Components.kt become 10 focused files: AppButton, Card, EmptyState, ErrorText, FieldLabel, Header, IconButton (w/ BackButton), Screen, Segmented, TextInput. Matches NowInAndroid style of one composable per file in a designsystem/component package. * chore(folio): consolidate uatu testing files under uatu/ folder Move spec.ts, package.json, package-lock.json into examples/folio/uatu so all uatu-specific testing artifacts live in one place. runs/ and node_modules/ follow the same convention (both remain gitignored). Update justfile, README, and the two Go consumers (bundle-check tool + verifier/trace tests) that referenced the old path. * refactor(trace): drop folio path in writer test Round-trip only needs a non-empty string; neutralize to keep the library free of folio references. * refactor(sidecar): neutralize ResolveActivity test fixtures Swap app.folio for com.example.app in the fixture strings so the sidecar tests don't reference the example app by name. * refactor(bundle-check): take spec path as argument Previously the tool hardcoded examples/folio/uatu/spec.ts. Accept a positional spec path instead so the tool works for any example and leaves no folio reference in the library surface. * test(verifier): add neutral integration spec and hierarchy fixtures Adds testdata/integration_spec.ts with two routes ("list", "form"), an InputText on text_field, a Tap on primary/secondary_action, a safety property (itemCountNonNegative), and a liveness property (submitEventually). Adds hierarchies_test.go with matching XML fixtures. Constants intentionally go in a _test.go at package root rather than testdata/hierarchies.go because go skips .go files under testdata/. * refactor(verifier): replace folio integration tests with neutral ones Renames bundleFolioSpec -> bundleIntegrationSpec and the three Test* entry points to TestIntegrationSpec*. Uses the synthetic spec and hierarchies added in the previous commit so the library's test suite no longer references examples/folio at all. Folio-specific coverage remains covered by examples/folio/justfile's 'just test' which exercises the real spec on device/emulator. * chore: remove cmd/uatu-sample-harness Not referenced by Makefile, docs, CI, or any script. Duplicates the adb reverse helpers already in cmd/uatu/test_run.go, and its name implies ownership by the sample app which violates the library/ example decoupling. If a bare-protocol debugging tool is later needed it belongs inside cmd/uatu/. * docs(folio): drop Layout section and KMP layout paragraph; fix AVD override syntax The directory-tree Layout section rots faster than the code and duplicates what ls shows for free. The expect/actual paragraph in Stack was the same kind of filler. The README also claimed 'just AVD=Pixel_7 test' but the justfile reads AVD as an env var via env_var_or_default, so the correct invocation is 'AVD=Pixel_7 just test'. --- README.md | 2 +- cmd/internal-tools/bundle-check/main.go | 17 +- cmd/uatu-sample-harness/main.go | 98 ------- docs/manual/getting-started.md | 4 +- examples/{sample-app => folio}/.gitignore | 3 +- examples/{sample-app => folio}/README.md | 41 +-- .../{sample-app => folio}/build.gradle.kts | 0 .../composeApp/build.gradle.kts | 8 +- .../src/androidMain/AndroidManifest.xml | 4 +- .../kotlin/app/folio/FolioApplication.kt} | 22 +- .../kotlin/app/folio}/MainActivity.kt | 2 +- .../app/folio/data}/DriverFactory.android.kt | 4 +- .../app/folio/data}/LedgerStore.android.kt | 2 +- .../app/folio/platform}/Format.android.kt | 2 +- .../app/folio/platform}/Platform.android.kt | 2 +- .../app/folio}/ui/BackHandler.android.kt | 2 +- .../src/commonMain/kotlin/app/folio}/App.kt | 31 ++- .../kotlin/app/folio}/FocusTracker.kt | 2 +- .../kotlin/app/folio/data/Account.kt | 7 + .../kotlin/app/folio/data}/LedgerStore.kt | 2 +- .../kotlin/app/folio/data}/Repository.kt | 3 +- .../kotlin/app/folio/data/Session.kt | 6 + .../kotlin/app/folio/data/Transaction.kt} | 13 +- .../feature/account/AddAccountScreen.kt} | 36 ++- .../feature/account/AddAccountUiState.kt | 7 + .../app/folio/feature/auth/LoginScreen.kt} | 40 ++- .../app/folio/feature/auth/LoginUiState.kt | 7 + .../app/folio/feature/home/HomeScreen.kt} | 28 +- .../feature/ledger/AddTransactionScreen.kt} | 49 ++-- .../feature/ledger/AddTransactionUiState.kt} | 6 +- .../app/folio/feature/ledger/LedgerScreen.kt} | 32 ++- .../kotlin/app/folio/navigation/Navigator.kt} | 10 +- .../kotlin/app/folio/navigation/Route.kt | 9 + .../kotlin/app/folio/platform}/Format.kt | 4 +- .../kotlin/app/folio/platform}/Platform.kt | 2 +- .../kotlin/app/folio}/ui/BackHandler.kt | 2 +- .../app/folio/ui/component/AppButton.kt | 57 ++++ .../kotlin/app/folio/ui/component/Card.kt | 33 +++ .../app/folio/ui/component/EmptyState.kt | 56 ++++ .../app/folio/ui/component/ErrorText.kt | 21 ++ .../app/folio/ui/component/FieldLabel.kt | 12 + .../kotlin/app/folio/ui/component/Header.kt | 48 ++++ .../app/folio/ui/component/IconButton.kt | 51 ++++ .../kotlin/app/folio/ui/component/Screen.kt | 53 ++++ .../app/folio/ui/component/Segmented.kt | 64 +++++ .../app/folio/ui/component/TextInput.kt | 95 +++++++ .../kotlin/app/folio/ui/icon}/Icons.kt | 2 +- .../kotlin/app/folio/ui/theme}/Theme.kt | 20 +- .../kotlin/app/folio/ui/theme/Type.kt | 20 ++ .../sqldelight/app/folio}/db/Ledger.sq | 0 .../kotlin/app/folio}/MainViewController.kt | 5 +- .../app/folio/data}/DriverFactory.ios.kt | 4 +- .../kotlin/app/folio/data}/LedgerStore.ios.kt | 2 +- .../kotlin/app/folio/platform}/Format.ios.kt | 2 +- .../app/folio/platform}/Platform.ios.kt | 2 +- .../kotlin/app/folio}/ui/BackHandler.ios.kt | 2 +- .../kotlin/app/folio}/ui/IosBackGesture.kt | 2 +- .../kotlin/app/folio/data}/DriverFactory.kt | 2 +- .../kotlin/app/folio/data}/SqlLedgerStore.kt | 6 +- .../kotlin/app/folio/data}/Snapshot.kt | 2 +- .../kotlin/app/folio/data}/WebLedgerStore.kt | 2 +- .../src/wasmJsMain/kotlin/app/folio}/main.kt | 5 +- .../app/folio/platform}/Format.wasmJs.kt | 2 +- .../app/folio/platform}/Platform.wasmJs.kt | 2 +- .../app/folio}/ui/BackHandler.wasmJs.kt | 2 +- .../src/wasmJsMain/resources/index.html | 2 +- .../{sample-app => folio}/gradle.properties | 2 +- .../gradle/wrapper/gradle-wrapper.jar | Bin .../gradle/wrapper/gradle-wrapper.properties | 0 examples/{sample-app => folio}/gradlew | 0 examples/{sample-app => folio}/gradlew.bat | 0 .../iosApp/iosApp/Info.plist | 4 +- .../iosApp/iosApp/iOSApp.swift | 0 .../{sample-app => folio}/iosApp/project.yml | 4 +- examples/{sample-app => folio}/justfile | 14 +- .../kotlin-js-store/wasm/yarn.lock | 0 .../{sample-app => folio}/settings.gradle.kts | 2 +- .../uatu}/package-lock.json | 4 +- .../{sample-app => folio/uatu}/package.json | 2 +- examples/{sample-app => folio/uatu}/spec.ts | 2 +- .../commonMain/kotlin/dev/uatu/sample/Auth.kt | 8 - .../kotlin/dev/uatu/sample/ui/Components.kt | 162 ----------- .../kotlin/dev/uatu/sample/ui/Widgets.kt | 208 -------------- internal/trace/writer_test.go | 2 +- internal/verifier/hierarchies_test.go | 19 ++ internal/verifier/spec_integration_test.go | 255 ++++-------------- .../verifier/testdata/integration_spec.ts | 68 +++++ .../dev/uatu/sidecar/ResolveActivityTest.kt | 8 +- 88 files changed, 949 insertions(+), 900 deletions(-) delete mode 100644 cmd/uatu-sample-harness/main.go rename examples/{sample-app => folio}/.gitignore (82%) rename examples/{sample-app => folio}/README.md (51%) rename examples/{sample-app => folio}/build.gradle.kts (100%) rename examples/{sample-app => folio}/composeApp/build.gradle.kts (94%) rename examples/{sample-app => folio}/composeApp/src/androidMain/AndroidManifest.xml (89%) rename examples/{sample-app/composeApp/src/androidMain/kotlin/dev/uatu/sample/SampleApplication.kt => folio/composeApp/src/androidMain/kotlin/app/folio/FolioApplication.kt} (79%) rename examples/{sample-app/composeApp/src/androidMain/kotlin/dev/uatu/sample => folio/composeApp/src/androidMain/kotlin/app/folio}/MainActivity.kt (92%) rename examples/{sample-app/composeApp/src/androidMain/kotlin/dev/uatu/sample => folio/composeApp/src/androidMain/kotlin/app/folio/data}/DriverFactory.android.kt (82%) rename examples/{sample-app/composeApp/src/androidMain/kotlin/dev/uatu/sample => folio/composeApp/src/androidMain/kotlin/app/folio/data}/LedgerStore.android.kt (89%) rename examples/{sample-app/composeApp/src/androidMain/kotlin/dev/uatu/sample => folio/composeApp/src/androidMain/kotlin/app/folio/platform}/Format.android.kt (89%) rename examples/{sample-app/composeApp/src/androidMain/kotlin/dev/uatu/sample => folio/composeApp/src/androidMain/kotlin/app/folio/platform}/Platform.android.kt (86%) rename examples/{sample-app/composeApp/src/androidMain/kotlin/dev/uatu/sample => folio/composeApp/src/androidMain/kotlin/app/folio}/ui/BackHandler.android.kt (88%) rename examples/{sample-app/composeApp/src/commonMain/kotlin/dev/uatu/sample => folio/composeApp/src/commonMain/kotlin/app/folio}/App.kt (69%) rename examples/{sample-app/composeApp/src/commonMain/kotlin/dev/uatu/sample => folio/composeApp/src/commonMain/kotlin/app/folio}/FocusTracker.kt (96%) create mode 100644 examples/folio/composeApp/src/commonMain/kotlin/app/folio/data/Account.kt rename examples/{sample-app/composeApp/src/commonMain/kotlin/dev/uatu/sample => folio/composeApp/src/commonMain/kotlin/app/folio/data}/LedgerStore.kt (95%) rename examples/{sample-app/composeApp/src/commonMain/kotlin/dev/uatu/sample => folio/composeApp/src/commonMain/kotlin/app/folio/data}/Repository.kt (96%) create mode 100644 examples/folio/composeApp/src/commonMain/kotlin/app/folio/data/Session.kt rename examples/{sample-app/composeApp/src/commonMain/kotlin/dev/uatu/sample/Models.kt => folio/composeApp/src/commonMain/kotlin/app/folio/data/Transaction.kt} (52%) rename examples/{sample-app/composeApp/src/commonMain/kotlin/dev/uatu/sample/ui/AddAccount.kt => folio/composeApp/src/commonMain/kotlin/app/folio/feature/account/AddAccountScreen.kt} (67%) create mode 100644 examples/folio/composeApp/src/commonMain/kotlin/app/folio/feature/account/AddAccountUiState.kt rename examples/{sample-app/composeApp/src/commonMain/kotlin/dev/uatu/sample/ui/Login.kt => folio/composeApp/src/commonMain/kotlin/app/folio/feature/auth/LoginScreen.kt} (71%) create mode 100644 examples/folio/composeApp/src/commonMain/kotlin/app/folio/feature/auth/LoginUiState.kt rename examples/{sample-app/composeApp/src/commonMain/kotlin/dev/uatu/sample/ui/Home.kt => folio/composeApp/src/commonMain/kotlin/app/folio/feature/home/HomeScreen.kt} (86%) rename examples/{sample-app/composeApp/src/commonMain/kotlin/dev/uatu/sample/ui/AddTransaction.kt => folio/composeApp/src/commonMain/kotlin/app/folio/feature/ledger/AddTransactionScreen.kt} (73%) rename examples/{sample-app/composeApp/src/commonMain/kotlin/dev/uatu/sample/UiState.kt => folio/composeApp/src/commonMain/kotlin/app/folio/feature/ledger/AddTransactionUiState.kt} (52%) rename examples/{sample-app/composeApp/src/commonMain/kotlin/dev/uatu/sample/ui/Ledger.kt => folio/composeApp/src/commonMain/kotlin/app/folio/feature/ledger/LedgerScreen.kt} (86%) rename examples/{sample-app/composeApp/src/commonMain/kotlin/dev/uatu/sample/Navigation.kt => folio/composeApp/src/commonMain/kotlin/app/folio/navigation/Navigator.kt} (74%) create mode 100644 examples/folio/composeApp/src/commonMain/kotlin/app/folio/navigation/Route.kt rename examples/{sample-app/composeApp/src/commonMain/kotlin/dev/uatu/sample => folio/composeApp/src/commonMain/kotlin/app/folio/platform}/Format.kt (95%) rename examples/{sample-app/composeApp/src/commonMain/kotlin/dev/uatu/sample => folio/composeApp/src/commonMain/kotlin/app/folio/platform}/Platform.kt (73%) rename examples/{sample-app/composeApp/src/commonMain/kotlin/dev/uatu/sample => folio/composeApp/src/commonMain/kotlin/app/folio}/ui/BackHandler.kt (78%) create mode 100644 examples/folio/composeApp/src/commonMain/kotlin/app/folio/ui/component/AppButton.kt create mode 100644 examples/folio/composeApp/src/commonMain/kotlin/app/folio/ui/component/Card.kt create mode 100644 examples/folio/composeApp/src/commonMain/kotlin/app/folio/ui/component/EmptyState.kt create mode 100644 examples/folio/composeApp/src/commonMain/kotlin/app/folio/ui/component/ErrorText.kt create mode 100644 examples/folio/composeApp/src/commonMain/kotlin/app/folio/ui/component/FieldLabel.kt create mode 100644 examples/folio/composeApp/src/commonMain/kotlin/app/folio/ui/component/Header.kt create mode 100644 examples/folio/composeApp/src/commonMain/kotlin/app/folio/ui/component/IconButton.kt create mode 100644 examples/folio/composeApp/src/commonMain/kotlin/app/folio/ui/component/Screen.kt create mode 100644 examples/folio/composeApp/src/commonMain/kotlin/app/folio/ui/component/Segmented.kt create mode 100644 examples/folio/composeApp/src/commonMain/kotlin/app/folio/ui/component/TextInput.kt rename examples/{sample-app/composeApp/src/commonMain/kotlin/dev/uatu/sample/ui => folio/composeApp/src/commonMain/kotlin/app/folio/ui/icon}/Icons.kt (97%) rename examples/{sample-app/composeApp/src/commonMain/kotlin/dev/uatu/sample/ui => folio/composeApp/src/commonMain/kotlin/app/folio/ui/theme}/Theme.kt (52%) create mode 100644 examples/folio/composeApp/src/commonMain/kotlin/app/folio/ui/theme/Type.kt rename examples/{sample-app/composeApp/src/commonMain/sqldelight/dev/uatu/sample => folio/composeApp/src/commonMain/sqldelight/app/folio}/db/Ledger.sq (100%) rename examples/{sample-app/composeApp/src/iosMain/kotlin/dev/uatu/sample => folio/composeApp/src/iosMain/kotlin/app/folio}/MainViewController.kt (93%) rename examples/{sample-app/composeApp/src/iosMain/kotlin/dev/uatu/sample => folio/composeApp/src/iosMain/kotlin/app/folio/data}/DriverFactory.ios.kt (78%) rename examples/{sample-app/composeApp/src/iosMain/kotlin/dev/uatu/sample => folio/composeApp/src/iosMain/kotlin/app/folio/data}/LedgerStore.ios.kt (76%) rename examples/{sample-app/composeApp/src/iosMain/kotlin/dev/uatu/sample => folio/composeApp/src/iosMain/kotlin/app/folio/platform}/Format.ios.kt (94%) rename examples/{sample-app/composeApp/src/iosMain/kotlin/dev/uatu/sample => folio/composeApp/src/iosMain/kotlin/app/folio/platform}/Platform.ios.kt (91%) rename examples/{sample-app/composeApp/src/iosMain/kotlin/dev/uatu/sample => folio/composeApp/src/iosMain/kotlin/app/folio}/ui/BackHandler.ios.kt (94%) rename examples/{sample-app/composeApp/src/iosMain/kotlin/dev/uatu/sample => folio/composeApp/src/iosMain/kotlin/app/folio}/ui/IosBackGesture.kt (94%) rename examples/{sample-app/composeApp/src/sqlMain/kotlin/dev/uatu/sample => folio/composeApp/src/sqlMain/kotlin/app/folio/data}/DriverFactory.kt (80%) rename examples/{sample-app/composeApp/src/sqlMain/kotlin/dev/uatu/sample => folio/composeApp/src/sqlMain/kotlin/app/folio/data}/SqlLedgerStore.kt (94%) rename examples/{sample-app/composeApp/src/wasmJsMain/kotlin/dev/uatu/sample => folio/composeApp/src/wasmJsMain/kotlin/app/folio/data}/Snapshot.kt (99%) rename examples/{sample-app/composeApp/src/wasmJsMain/kotlin/dev/uatu/sample => folio/composeApp/src/wasmJsMain/kotlin/app/folio/data}/WebLedgerStore.kt (98%) rename examples/{sample-app/composeApp/src/wasmJsMain/kotlin/dev/uatu/sample => folio/composeApp/src/wasmJsMain/kotlin/app/folio}/main.kt (87%) rename examples/{sample-app/composeApp/src/wasmJsMain/kotlin/dev/uatu/sample => folio/composeApp/src/wasmJsMain/kotlin/app/folio/platform}/Format.wasmJs.kt (97%) rename examples/{sample-app/composeApp/src/wasmJsMain/kotlin/dev/uatu/sample => folio/composeApp/src/wasmJsMain/kotlin/app/folio/platform}/Platform.wasmJs.kt (96%) rename examples/{sample-app/composeApp/src/wasmJsMain/kotlin/dev/uatu/sample => folio/composeApp/src/wasmJsMain/kotlin/app/folio}/ui/BackHandler.wasmJs.kt (97%) rename examples/{sample-app => folio}/composeApp/src/wasmJsMain/resources/index.html (94%) rename examples/{sample-app => folio}/gradle.properties (78%) rename examples/{sample-app => folio}/gradle/wrapper/gradle-wrapper.jar (100%) rename examples/{sample-app => folio}/gradle/wrapper/gradle-wrapper.properties (100%) rename examples/{sample-app => folio}/gradlew (100%) rename examples/{sample-app => folio}/gradlew.bat (100%) rename examples/{sample-app => folio}/iosApp/iosApp/Info.plist (94%) rename examples/{sample-app => folio}/iosApp/iosApp/iOSApp.swift (100%) rename examples/{sample-app => folio}/iosApp/project.yml (93%) rename examples/{sample-app => folio}/justfile (89%) rename examples/{sample-app => folio}/kotlin-js-store/wasm/yarn.lock (100%) rename examples/{sample-app => folio}/settings.gradle.kts (90%) rename examples/{sample-app => folio/uatu}/package-lock.json (89%) rename examples/{sample-app => folio/uatu}/package.json (83%) rename examples/{sample-app => folio/uatu}/spec.ts (99%) delete mode 100644 examples/sample-app/composeApp/src/commonMain/kotlin/dev/uatu/sample/Auth.kt delete mode 100644 examples/sample-app/composeApp/src/commonMain/kotlin/dev/uatu/sample/ui/Components.kt delete mode 100644 examples/sample-app/composeApp/src/commonMain/kotlin/dev/uatu/sample/ui/Widgets.kt create mode 100644 internal/verifier/hierarchies_test.go create mode 100644 internal/verifier/testdata/integration_spec.ts diff --git a/README.md b/README.md index ae4864b..c55e14e 100644 --- a/README.md +++ b/README.md @@ -8,5 +8,5 @@ Autonomous property-based testing for mobile apps. Specs in TypeScript. Core in - [Getting started](https://priyanshujain.github.io/uatu/manual/getting-started.html) - [Writing specs](https://priyanshujain.github.io/uatu/manual/writing-specs.html) -- [Example](https://github.com/priyanshujain/uatu/tree/master/examples/sample-app) +- [Example](https://github.com/priyanshujain/uatu/tree/master/examples/folio) - [Architecture](https://priyanshujain.github.io/uatu/development/architecture.html) diff --git a/cmd/internal-tools/bundle-check/main.go b/cmd/internal-tools/bundle-check/main.go index 17c0c81..a30111b 100644 --- a/cmd/internal-tools/bundle-check/main.go +++ b/cmd/internal-tools/bundle-check/main.go @@ -9,17 +9,30 @@ import ( ) func main() { + if len(os.Args) < 2 { + fmt.Fprintln(os.Stderr, "usage: bundle-check <spec.ts>") + os.Exit(1) + } + + entryFile, err := filepath.Abs(os.Args[1]) + if err != nil { + fmt.Fprintf(os.Stderr, "resolve spec path: %v\n", err) + os.Exit(1) + } + repoRoot, err := os.Getwd() if err != nil { fmt.Fprintf(os.Stderr, "getwd: %v\n", err) os.Exit(1) } specApiPath := filepath.Join(repoRoot, "pkg/spec-api/src/index.ts") + defaultPropertiesPath := filepath.Join(repoRoot, "pkg/spec-api/src/defaults/properties.ts") result, err := bundler.Bundle(bundler.Options{ - EntryFile: "examples/sample-app/spec.ts", + EntryFile: entryFile, Aliases: map[string]string{ - "@uatu/spec": specApiPath, + "@uatu/spec": specApiPath, + "@uatu/spec/defaults/properties": defaultPropertiesPath, }, }) if err != nil { diff --git a/cmd/uatu-sample-harness/main.go b/cmd/uatu-sample-harness/main.go deleted file mode 100644 index 786c933..0000000 --- a/cmd/uatu-sample-harness/main.go +++ /dev/null @@ -1,98 +0,0 @@ -package main - -import ( - "context" - "encoding/json" - "flag" - "fmt" - "net" - "os" - "os/exec" - "os/signal" - "syscall" - "time" - - "github.com/priyanshujain/uatu/internal/agent" -) - -func main() { - port := flag.Int("port", 0, "TCP port to listen on (0 = ephemeral)") - socketName := flag.String("socket-name", "uatu-agent", "abstract socket name on the device") - cycles := flag.Int("cycles", 3, "number of snapshot cycles to run") - serial := flag.String("serial", "", "adb device serial (empty = default)") - cycleInterval := flag.Duration("interval", 500*time.Millisecond, "delay between snapshot cycles") - flag.Parse() - - listener, err := net.Listen("tcp", fmt.Sprintf("127.0.0.1:%d", *port)) - if err != nil { - die("listen: %v", err) - } - defer listener.Close() - listenerAddr := listener.Addr().(*net.TCPAddr) - fmt.Printf("listening on 127.0.0.1:%d\n", listenerAddr.Port) - - if err := adbReverse(*serial, *socketName, listenerAddr.Port); err != nil { - die("adb reverse: %v", err) - } - defer func() { - if err := adbReverseRemove(*serial, *socketName); err != nil { - fmt.Fprintf(os.Stderr, "adb reverse cleanup: %v\n", err) - } - }() - fmt.Printf("forwarded localabstract:%s -> tcp:%d on device\n", *socketName, listenerAddr.Port) - - ctx, cancel := signal.NotifyContext(context.Background(), syscall.SIGINT, syscall.SIGTERM) - defer cancel() - - server := agent.NewServer(listener) - fmt.Println("waiting for SDK to connect (launch the sample app on the device)...") - conn, err := server.Accept(ctx) - if err != nil { - die("accept: %v", err) - } - defer conn.Close() - hello := conn.Hello() - fmt.Printf("HELLO from platform=%s app=%s sdk=%s\n", hello.Platform, hello.AppPackage, hello.Version) - - for index := 1; index <= *cycles; index++ { - snapshotCtx, snapshotCancel := context.WithTimeout(ctx, 5*time.Second) - state, err := conn.Snapshot(snapshotCtx) - snapshotCancel() - if err != nil { - die("snapshot #%d: %v", index, err) - } - marshalled, _ := json.Marshal(state.Snapshots) - fmt.Printf("STATE %d: %s\n", index, marshalled) - if err := conn.Release(ctx); err != nil { - die("release #%d: %v", index, err) - } - if index < *cycles { - time.Sleep(*cycleInterval) - } - } - fmt.Println("round-trip complete") -} - -func adbReverse(serial, socketName string, port int) error { - commandArguments := []string{"reverse", fmt.Sprintf("localabstract:%s", socketName), fmt.Sprintf("tcp:%d", port)} - if serial != "" { - commandArguments = append([]string{"-s", serial}, commandArguments...) - } - command := exec.Command("adb", commandArguments...) - command.Stdout = os.Stdout - command.Stderr = os.Stderr - return command.Run() -} - -func adbReverseRemove(serial, socketName string) error { - commandArguments := []string{"reverse", "--remove", fmt.Sprintf("localabstract:%s", socketName)} - if serial != "" { - commandArguments = append([]string{"-s", serial}, commandArguments...) - } - return exec.Command("adb", commandArguments...).Run() -} - -func die(format string, args ...any) { - fmt.Fprintf(os.Stderr, "error: "+format+"\n", args...) - os.Exit(1) -} diff --git a/docs/manual/getting-started.md b/docs/manual/getting-started.md index 898f937..9c995b1 100644 --- a/docs/manual/getting-started.md +++ b/docs/manual/getting-started.md @@ -50,14 +50,14 @@ dependencies { ## Your first run -The repo ships a working sample at `examples/sample-app`. From that directory: +The repo ships a working sample at `examples/folio`. From that directory: ```sh npm install (cd android && ./gradlew installDebug) uatu test \ --spec spec.ts \ - --bundle-id dev.uatu.sample \ + --bundle-id app.folio \ --platform android \ --duration 2m ``` diff --git a/examples/sample-app/.gitignore b/examples/folio/.gitignore similarity index 82% rename from examples/sample-app/.gitignore rename to examples/folio/.gitignore index 9d44317..52e8ba2 100644 --- a/examples/sample-app/.gitignore +++ b/examples/folio/.gitignore @@ -6,6 +6,7 @@ iosApp/build/ iosApp/iosApp.xcodeproj/ iosApp/iosApp.xcodeproj/** .DS_Store -runs/ +uatu/node_modules/ +uatu/runs/ .playwright-mcp/ web-*.png diff --git a/examples/sample-app/README.md b/examples/folio/README.md similarity index 51% rename from examples/sample-app/README.md rename to examples/folio/README.md index 789445f..26f391b 100644 --- a/examples/sample-app/README.md +++ b/examples/folio/README.md @@ -1,9 +1,9 @@ -# uatu sample app +# Folio -A minimal Kotlin Multiplatform ledger app that mirrors the React reference: -login with demo credentials, create accounts, add credits and debits. Same -features, monospace look, and demo creds, shared across Android and iOS via -Compose Multiplatform. +A minimal Kotlin Multiplatform personal-ledger app: login with demo +credentials, create accounts, add credits and debits. Shared UI across +Android, iOS, and Web via Compose Multiplatform. Doubles as the example +uatu runs its property-based specs against. ## Stack @@ -12,10 +12,6 @@ Compose Multiplatform. - kotlinx.coroutines for state flows - uatu `sdk-android` for harness integration on Android -Everything in `composeApp/src/commonMain/kotlin/dev/uatu/sample/` is shared -between platforms. Platform-specific I/O (file storage, clock, UUID) lives -in `androidMain/` and `iosMain/` as `actual`s of the `Platform` expect object. - ## Prerequisites - `just` @@ -46,7 +42,7 @@ launches. ## Demo credentials ``` -email: demo@ledger.app +email: demo@folio.app password: ledger123 ``` @@ -60,7 +56,7 @@ If no device is connected, uatu boots the single AVD it finds. With multiple AVDs, pick one: ```sh -just AVD=Pixel_7 test +AVD=Pixel_7 just test ``` Persistent settings can live in `.env` alongside the justfile: @@ -70,28 +66,13 @@ AVD=Pixel_7 DURATION=5m ``` -Traces land in `./runs/<timestamp>/`. - -## Layout - -``` -composeApp/ - src/commonMain/kotlin/dev/uatu/sample/ shared domain, state, UI - src/androidMain/ Android Application + Activity - src/iosMain/ iOS UIViewController entry -iosApp/ - project.yml xcodegen spec - iosApp/iOSApp.swift SwiftUI host - iosApp/Info.plist -justfile -spec.ts uatu test spec -``` +Traces land in `./uatu/runs/<timestamp>/`. ## How it connects to uatu -- `composeApp/src/androidMain/.../SampleApplication.kt` calls `Uatu.start(this)` +- `composeApp/src/androidMain/.../FolioApplication.kt` calls `Uatu.start(this)` and registers snapshot extractors (`logged_in`, `account_count`, `total_balance`, `route`) -- `spec.ts` imports `@uatu/spec`, reads those snapshots, asserts properties, - and weights the actions the fuzzer picks from +- `uatu/spec.ts` imports `@uatu/spec`, reads those snapshots, asserts + properties, and weights the actions the fuzzer picks from - `just test` invokes `uatu test` against the installed APK diff --git a/examples/sample-app/build.gradle.kts b/examples/folio/build.gradle.kts similarity index 100% rename from examples/sample-app/build.gradle.kts rename to examples/folio/build.gradle.kts diff --git a/examples/sample-app/composeApp/build.gradle.kts b/examples/folio/composeApp/build.gradle.kts similarity index 94% rename from examples/sample-app/composeApp/build.gradle.kts rename to examples/folio/composeApp/build.gradle.kts index 678b0fa..fd96ece 100644 --- a/examples/sample-app/composeApp/build.gradle.kts +++ b/examples/folio/composeApp/build.gradle.kts @@ -35,7 +35,7 @@ kotlin { target.binaries.framework { baseName = "ComposeApp" isStatic = true - binaryOption("bundleId", "dev.uatu.sample") + binaryOption("bundleId", "app.folio") } } @@ -93,11 +93,11 @@ kotlin { } android { - namespace = "dev.uatu.sample" + namespace = "app.folio" compileSdk = 36 defaultConfig { - applicationId = "dev.uatu.sample" + applicationId = "app.folio" minSdk = 24 targetSdk = 36 versionCode = 1 @@ -124,7 +124,7 @@ android { sqldelight { databases { create("LedgerDatabase") { - packageName.set("dev.uatu.sample.db") + packageName.set("app.folio.db") } } } diff --git a/examples/sample-app/composeApp/src/androidMain/AndroidManifest.xml b/examples/folio/composeApp/src/androidMain/AndroidManifest.xml similarity index 89% rename from examples/sample-app/composeApp/src/androidMain/AndroidManifest.xml rename to examples/folio/composeApp/src/androidMain/AndroidManifest.xml index bbcfc8e..748409f 100644 --- a/examples/sample-app/composeApp/src/androidMain/AndroidManifest.xml +++ b/examples/folio/composeApp/src/androidMain/AndroidManifest.xml @@ -1,8 +1,8 @@ <?xml version="1.0" encoding="utf-8"?> <manifest xmlns:android="http://schemas.android.com/apk/res/android"> <application - android:name=".SampleApplication" - android:label="Uatu Sample" + android:name=".FolioApplication" + android:label="Folio" android:allowBackup="false" android:theme="@android:style/Theme.Material.Light.NoActionBar"> <activity diff --git a/examples/sample-app/composeApp/src/androidMain/kotlin/dev/uatu/sample/SampleApplication.kt b/examples/folio/composeApp/src/androidMain/kotlin/app/folio/FolioApplication.kt similarity index 79% rename from examples/sample-app/composeApp/src/androidMain/kotlin/dev/uatu/sample/SampleApplication.kt rename to examples/folio/composeApp/src/androidMain/kotlin/app/folio/FolioApplication.kt index 3c95a65..1d90bbf 100644 --- a/examples/sample-app/composeApp/src/androidMain/kotlin/dev/uatu/sample/SampleApplication.kt +++ b/examples/folio/composeApp/src/androidMain/kotlin/app/folio/FolioApplication.kt @@ -1,9 +1,19 @@ -package dev.uatu.sample +package app.folio import android.app.Application +import app.folio.data.AndroidLedgerContext +import app.folio.data.Repository +import app.folio.data.TxnType +import app.folio.feature.account.AddAccountUiState +import app.folio.feature.auth.LoginUiState +import app.folio.feature.ledger.AddTransactionUiState +import app.folio.navigation.Navigator +import app.folio.navigation.Route +import app.folio.platform.balanceOf +import app.folio.platform.signedAmount import dev.uatu.sdk.Uatu -class SampleApplication : Application() { +class FolioApplication : Application() { override fun onCreate() { super.onCreate() AndroidLedgerContext.context = applicationContext @@ -72,12 +82,12 @@ class SampleApplication : Application() { else balanceOf(Repository.transactions.value.filter { it.accountId == active }) } Uatu.extract("focused_input") { FocusTracker.current.value } - Uatu.extract("txn_form_type") { UiState.txnFormType.value } + Uatu.extract("txn_form_type") { AddTransactionUiState.txnFormType.value } Uatu.extract("txn_form_account_id") { (Navigator.current.value as? Route.AddTransaction)?.accountId } - Uatu.extract("login_error") { UiState.loginError.value } - Uatu.extract("add_account_error") { UiState.addAccountError.value } - Uatu.extract("txn_error") { UiState.txnError.value } + Uatu.extract("login_error") { LoginUiState.loginError.value } + Uatu.extract("add_account_error") { AddAccountUiState.addAccountError.value } + Uatu.extract("txn_error") { AddTransactionUiState.txnError.value } } } diff --git a/examples/sample-app/composeApp/src/androidMain/kotlin/dev/uatu/sample/MainActivity.kt b/examples/folio/composeApp/src/androidMain/kotlin/app/folio/MainActivity.kt similarity index 92% rename from examples/sample-app/composeApp/src/androidMain/kotlin/dev/uatu/sample/MainActivity.kt rename to examples/folio/composeApp/src/androidMain/kotlin/app/folio/MainActivity.kt index 1042d2b..e7b0fb6 100644 --- a/examples/sample-app/composeApp/src/androidMain/kotlin/dev/uatu/sample/MainActivity.kt +++ b/examples/folio/composeApp/src/androidMain/kotlin/app/folio/MainActivity.kt @@ -1,4 +1,4 @@ -package dev.uatu.sample +package app.folio import android.os.Bundle import androidx.activity.ComponentActivity diff --git a/examples/sample-app/composeApp/src/androidMain/kotlin/dev/uatu/sample/DriverFactory.android.kt b/examples/folio/composeApp/src/androidMain/kotlin/app/folio/data/DriverFactory.android.kt similarity index 82% rename from examples/sample-app/composeApp/src/androidMain/kotlin/dev/uatu/sample/DriverFactory.android.kt rename to examples/folio/composeApp/src/androidMain/kotlin/app/folio/data/DriverFactory.android.kt index 6f2af53..ff19f55 100644 --- a/examples/sample-app/composeApp/src/androidMain/kotlin/dev/uatu/sample/DriverFactory.android.kt +++ b/examples/folio/composeApp/src/androidMain/kotlin/app/folio/data/DriverFactory.android.kt @@ -1,9 +1,9 @@ -package dev.uatu.sample +package app.folio.data import android.content.Context import app.cash.sqldelight.db.SqlDriver import app.cash.sqldelight.driver.android.AndroidSqliteDriver -import dev.uatu.sample.db.LedgerDatabase +import app.folio.db.LedgerDatabase actual class DriverFactory(private val context: Context) { actual fun create(): SqlDriver = diff --git a/examples/sample-app/composeApp/src/androidMain/kotlin/dev/uatu/sample/LedgerStore.android.kt b/examples/folio/composeApp/src/androidMain/kotlin/app/folio/data/LedgerStore.android.kt similarity index 89% rename from examples/sample-app/composeApp/src/androidMain/kotlin/dev/uatu/sample/LedgerStore.android.kt rename to examples/folio/composeApp/src/androidMain/kotlin/app/folio/data/LedgerStore.android.kt index f3a63f0..84c706f 100644 --- a/examples/sample-app/composeApp/src/androidMain/kotlin/dev/uatu/sample/LedgerStore.android.kt +++ b/examples/folio/composeApp/src/androidMain/kotlin/app/folio/data/LedgerStore.android.kt @@ -1,4 +1,4 @@ -package dev.uatu.sample +package app.folio.data import android.content.Context diff --git a/examples/sample-app/composeApp/src/androidMain/kotlin/dev/uatu/sample/Format.android.kt b/examples/folio/composeApp/src/androidMain/kotlin/app/folio/platform/Format.android.kt similarity index 89% rename from examples/sample-app/composeApp/src/androidMain/kotlin/dev/uatu/sample/Format.android.kt rename to examples/folio/composeApp/src/androidMain/kotlin/app/folio/platform/Format.android.kt index 312ba0e..ede6eed 100644 --- a/examples/sample-app/composeApp/src/androidMain/kotlin/dev/uatu/sample/Format.android.kt +++ b/examples/folio/composeApp/src/androidMain/kotlin/app/folio/platform/Format.android.kt @@ -1,4 +1,4 @@ -package dev.uatu.sample +package app.folio.platform import java.text.SimpleDateFormat import java.util.Date diff --git a/examples/sample-app/composeApp/src/androidMain/kotlin/dev/uatu/sample/Platform.android.kt b/examples/folio/composeApp/src/androidMain/kotlin/app/folio/platform/Platform.android.kt similarity index 86% rename from examples/sample-app/composeApp/src/androidMain/kotlin/dev/uatu/sample/Platform.android.kt rename to examples/folio/composeApp/src/androidMain/kotlin/app/folio/platform/Platform.android.kt index 48c8584..9adf3f6 100644 --- a/examples/sample-app/composeApp/src/androidMain/kotlin/dev/uatu/sample/Platform.android.kt +++ b/examples/folio/composeApp/src/androidMain/kotlin/app/folio/platform/Platform.android.kt @@ -1,4 +1,4 @@ -package dev.uatu.sample +package app.folio.platform import java.util.UUID diff --git a/examples/sample-app/composeApp/src/androidMain/kotlin/dev/uatu/sample/ui/BackHandler.android.kt b/examples/folio/composeApp/src/androidMain/kotlin/app/folio/ui/BackHandler.android.kt similarity index 88% rename from examples/sample-app/composeApp/src/androidMain/kotlin/dev/uatu/sample/ui/BackHandler.android.kt rename to examples/folio/composeApp/src/androidMain/kotlin/app/folio/ui/BackHandler.android.kt index bdb78d7..c2a3b2d 100644 --- a/examples/sample-app/composeApp/src/androidMain/kotlin/dev/uatu/sample/ui/BackHandler.android.kt +++ b/examples/folio/composeApp/src/androidMain/kotlin/app/folio/ui/BackHandler.android.kt @@ -1,4 +1,4 @@ -package dev.uatu.sample.ui +package app.folio.ui import androidx.compose.runtime.Composable import androidx.activity.compose.BackHandler as AndroidBackHandler diff --git a/examples/sample-app/composeApp/src/commonMain/kotlin/dev/uatu/sample/App.kt b/examples/folio/composeApp/src/commonMain/kotlin/app/folio/App.kt similarity index 69% rename from examples/sample-app/composeApp/src/commonMain/kotlin/dev/uatu/sample/App.kt rename to examples/folio/composeApp/src/commonMain/kotlin/app/folio/App.kt index 05caa86..052ccd0 100644 --- a/examples/sample-app/composeApp/src/commonMain/kotlin/dev/uatu/sample/App.kt +++ b/examples/folio/composeApp/src/commonMain/kotlin/app/folio/App.kt @@ -1,4 +1,4 @@ -package dev.uatu.sample +package app.folio import androidx.compose.foundation.background import androidx.compose.foundation.layout.Box @@ -13,14 +13,17 @@ import androidx.compose.runtime.CompositionLocalProvider import androidx.compose.runtime.collectAsState import androidx.compose.runtime.getValue import androidx.compose.ui.Modifier -import dev.uatu.sample.ui.AddAccountPage -import dev.uatu.sample.ui.AddTransactionPage -import dev.uatu.sample.ui.HomePage -import dev.uatu.sample.ui.LedgerPage -import dev.uatu.sample.ui.LedgerTheme -import dev.uatu.sample.ui.LocalTokens -import dev.uatu.sample.ui.LoginPage -import dev.uatu.sample.ui.Tokens +import app.folio.data.Repository +import app.folio.feature.account.AddAccountScreen +import app.folio.feature.auth.LoginScreen +import app.folio.feature.home.HomeScreen +import app.folio.feature.ledger.AddTransactionScreen +import app.folio.feature.ledger.LedgerScreen +import app.folio.navigation.Navigator +import app.folio.navigation.Route +import app.folio.ui.theme.LedgerTheme +import app.folio.ui.theme.LocalTokens +import app.folio.ui.theme.Tokens @Composable fun App() { @@ -46,17 +49,17 @@ fun App() { ) { Column(Modifier.fillMaxSize()) { when (val r = route) { - Route.Login -> LoginPage(onLoggedIn = { Navigator.replace(Route.Home) }) - Route.Home -> HomePage( + Route.Login -> LoginScreen(onLoggedIn = { Navigator.replace(Route.Home) }) + Route.Home -> HomeScreen( user = session?.user ?: "", onLogout = { Repository.clearSession() Navigator.replace(Route.Login) }, ) - Route.AddAccount -> AddAccountPage() - is Route.Ledger -> LedgerPage(accountId = r.accountId) - is Route.AddTransaction -> AddTransactionPage(accountId = r.accountId) + Route.AddAccount -> AddAccountScreen() + is Route.Ledger -> LedgerScreen(accountId = r.accountId) + is Route.AddTransaction -> AddTransactionScreen(accountId = r.accountId) } } } diff --git a/examples/sample-app/composeApp/src/commonMain/kotlin/dev/uatu/sample/FocusTracker.kt b/examples/folio/composeApp/src/commonMain/kotlin/app/folio/FocusTracker.kt similarity index 96% rename from examples/sample-app/composeApp/src/commonMain/kotlin/dev/uatu/sample/FocusTracker.kt rename to examples/folio/composeApp/src/commonMain/kotlin/app/folio/FocusTracker.kt index fe95708..b0e464a 100644 --- a/examples/sample-app/composeApp/src/commonMain/kotlin/dev/uatu/sample/FocusTracker.kt +++ b/examples/folio/composeApp/src/commonMain/kotlin/app/folio/FocusTracker.kt @@ -1,4 +1,4 @@ -package dev.uatu.sample +package app.folio import kotlinx.coroutines.flow.MutableStateFlow import kotlinx.coroutines.flow.StateFlow diff --git a/examples/folio/composeApp/src/commonMain/kotlin/app/folio/data/Account.kt b/examples/folio/composeApp/src/commonMain/kotlin/app/folio/data/Account.kt new file mode 100644 index 0000000..5601094 --- /dev/null +++ b/examples/folio/composeApp/src/commonMain/kotlin/app/folio/data/Account.kt @@ -0,0 +1,7 @@ +package app.folio.data + +data class Account( + val id: String, + val name: String, + val createdAt: Long, +) diff --git a/examples/sample-app/composeApp/src/commonMain/kotlin/dev/uatu/sample/LedgerStore.kt b/examples/folio/composeApp/src/commonMain/kotlin/app/folio/data/LedgerStore.kt similarity index 95% rename from examples/sample-app/composeApp/src/commonMain/kotlin/dev/uatu/sample/LedgerStore.kt rename to examples/folio/composeApp/src/commonMain/kotlin/app/folio/data/LedgerStore.kt index 75fe0c0..29f4a0e 100644 --- a/examples/sample-app/composeApp/src/commonMain/kotlin/dev/uatu/sample/LedgerStore.kt +++ b/examples/folio/composeApp/src/commonMain/kotlin/app/folio/data/LedgerStore.kt @@ -1,4 +1,4 @@ -package dev.uatu.sample +package app.folio.data import kotlinx.coroutines.flow.StateFlow diff --git a/examples/sample-app/composeApp/src/commonMain/kotlin/dev/uatu/sample/Repository.kt b/examples/folio/composeApp/src/commonMain/kotlin/app/folio/data/Repository.kt similarity index 96% rename from examples/sample-app/composeApp/src/commonMain/kotlin/dev/uatu/sample/Repository.kt rename to examples/folio/composeApp/src/commonMain/kotlin/app/folio/data/Repository.kt index c79b8fc..38293c3 100644 --- a/examples/sample-app/composeApp/src/commonMain/kotlin/dev/uatu/sample/Repository.kt +++ b/examples/folio/composeApp/src/commonMain/kotlin/app/folio/data/Repository.kt @@ -1,5 +1,6 @@ -package dev.uatu.sample +package app.folio.data +import app.folio.platform.Platform import kotlinx.coroutines.flow.StateFlow object Repository { diff --git a/examples/folio/composeApp/src/commonMain/kotlin/app/folio/data/Session.kt b/examples/folio/composeApp/src/commonMain/kotlin/app/folio/data/Session.kt new file mode 100644 index 0000000..0e0929f --- /dev/null +++ b/examples/folio/composeApp/src/commonMain/kotlin/app/folio/data/Session.kt @@ -0,0 +1,6 @@ +package app.folio.data + +data class Session( + val user: String, + val loggedInAt: Long, +) diff --git a/examples/sample-app/composeApp/src/commonMain/kotlin/dev/uatu/sample/Models.kt b/examples/folio/composeApp/src/commonMain/kotlin/app/folio/data/Transaction.kt similarity index 52% rename from examples/sample-app/composeApp/src/commonMain/kotlin/dev/uatu/sample/Models.kt rename to examples/folio/composeApp/src/commonMain/kotlin/app/folio/data/Transaction.kt index 2661f55..c9f1a54 100644 --- a/examples/sample-app/composeApp/src/commonMain/kotlin/dev/uatu/sample/Models.kt +++ b/examples/folio/composeApp/src/commonMain/kotlin/app/folio/data/Transaction.kt @@ -1,10 +1,4 @@ -package dev.uatu.sample - -data class Account( - val id: String, - val name: String, - val createdAt: Long, -) +package app.folio.data enum class TxnType { credit, debit } @@ -16,8 +10,3 @@ data class Transaction( val note: String, val createdAt: Long, ) - -data class Session( - val user: String, - val loggedInAt: Long, -) diff --git a/examples/sample-app/composeApp/src/commonMain/kotlin/dev/uatu/sample/ui/AddAccount.kt b/examples/folio/composeApp/src/commonMain/kotlin/app/folio/feature/account/AddAccountScreen.kt similarity index 67% rename from examples/sample-app/composeApp/src/commonMain/kotlin/dev/uatu/sample/ui/AddAccount.kt rename to examples/folio/composeApp/src/commonMain/kotlin/app/folio/feature/account/AddAccountScreen.kt index 3b48ed3..5c6286e 100644 --- a/examples/sample-app/composeApp/src/commonMain/kotlin/dev/uatu/sample/ui/AddAccount.kt +++ b/examples/folio/composeApp/src/commonMain/kotlin/app/folio/feature/account/AddAccountScreen.kt @@ -1,4 +1,4 @@ -package dev.uatu.sample.ui +package app.folio.feature.account import androidx.compose.foundation.layout.Arrangement import androidx.compose.foundation.layout.Column @@ -14,37 +14,47 @@ import androidx.compose.runtime.remember import androidx.compose.runtime.setValue import androidx.compose.ui.Modifier import androidx.compose.ui.unit.dp -import dev.uatu.sample.Navigator -import dev.uatu.sample.Repository -import dev.uatu.sample.Route -import dev.uatu.sample.UiState +import app.folio.data.Repository +import app.folio.navigation.Navigator +import app.folio.navigation.Route +import app.folio.ui.BackHandler +import app.folio.ui.component.AppButton +import app.folio.ui.component.BackButton +import app.folio.ui.component.ButtonStyle +import app.folio.ui.component.ErrorText +import app.folio.ui.component.FieldLabel +import app.folio.ui.component.Header +import app.folio.ui.component.Screen +import app.folio.ui.component.TextInput +import app.folio.ui.theme.LocalTokens +import app.folio.ui.theme.Type @Composable -fun AddAccountPage() { +fun AddAccountScreen() { val t = LocalTokens.current var name by remember { mutableStateOf("") } - val err by UiState.addAccountError.collectAsState() + val err by AddAccountUiState.addAccountError.collectAsState() BackHandler { Navigator.back(Route.Home) } DisposableEffect(Unit) { - onDispose { UiState.addAccountError.value = "" } + onDispose { AddAccountUiState.addAccountError.value = "" } } fun submit() { val trimmed = name.trim() if (trimmed.isEmpty()) { - UiState.addAccountError.value = "Account name is required"; return + AddAccountUiState.addAccountError.value = "Account name is required"; return } if (trimmed.length > 40) { - UiState.addAccountError.value = "Name is too long (max 40 characters)"; return + AddAccountUiState.addAccountError.value = "Name is too long (max 40 characters)"; return } try { Repository.createAccount(trimmed) - UiState.addAccountError.value = "" + AddAccountUiState.addAccountError.value = "" Navigator.replace(Route.Home) } catch (e: IllegalArgumentException) { - UiState.addAccountError.value = e.message ?: "Could not create account" + AddAccountUiState.addAccountError.value = e.message ?: "Could not create account" } } @@ -70,7 +80,7 @@ fun AddAccountPage() { FieldLabel("Account name") TextInput( value = name, - onChange = { name = it; UiState.addAccountError.value = "" }, + onChange = { name = it; AddAccountUiState.addAccountError.value = "" }, placeholder = "e.g. Checking", invalid = err.isNotEmpty(), label = "Account name", diff --git a/examples/folio/composeApp/src/commonMain/kotlin/app/folio/feature/account/AddAccountUiState.kt b/examples/folio/composeApp/src/commonMain/kotlin/app/folio/feature/account/AddAccountUiState.kt new file mode 100644 index 0000000..3c78d4e --- /dev/null +++ b/examples/folio/composeApp/src/commonMain/kotlin/app/folio/feature/account/AddAccountUiState.kt @@ -0,0 +1,7 @@ +package app.folio.feature.account + +import kotlinx.coroutines.flow.MutableStateFlow + +object AddAccountUiState { + val addAccountError = MutableStateFlow("") +} diff --git a/examples/sample-app/composeApp/src/commonMain/kotlin/dev/uatu/sample/ui/Login.kt b/examples/folio/composeApp/src/commonMain/kotlin/app/folio/feature/auth/LoginScreen.kt similarity index 71% rename from examples/sample-app/composeApp/src/commonMain/kotlin/dev/uatu/sample/ui/Login.kt rename to examples/folio/composeApp/src/commonMain/kotlin/app/folio/feature/auth/LoginScreen.kt index f0b4a67..b5066bc 100644 --- a/examples/sample-app/composeApp/src/commonMain/kotlin/dev/uatu/sample/ui/Login.kt +++ b/examples/folio/composeApp/src/commonMain/kotlin/app/folio/feature/auth/LoginScreen.kt @@ -1,4 +1,4 @@ -package dev.uatu.sample.ui +package app.folio.feature.auth import androidx.compose.foundation.layout.Arrangement import androidx.compose.foundation.layout.Column @@ -17,31 +17,43 @@ import androidx.compose.runtime.setValue import androidx.compose.ui.Modifier import androidx.compose.ui.text.input.KeyboardType import androidx.compose.ui.unit.dp -import dev.uatu.sample.DEMO_EMAIL -import dev.uatu.sample.DEMO_PASSWORD -import dev.uatu.sample.Repository -import dev.uatu.sample.UiState -import dev.uatu.sample.checkCredentials +import app.folio.data.Repository +import app.folio.ui.component.AppButton +import app.folio.ui.component.ButtonStyle +import app.folio.ui.component.Card +import app.folio.ui.component.ErrorText +import app.folio.ui.component.FieldLabel +import app.folio.ui.component.Screen +import app.folio.ui.component.TextInput +import app.folio.ui.theme.LocalTokens +import app.folio.ui.theme.Type + +const val DEMO_EMAIL = "demo@folio.app" +const val DEMO_PASSWORD = "ledger123" + +private fun checkCredentials(email: String, password: String): Boolean { + return email.trim().lowercase() == DEMO_EMAIL && password == DEMO_PASSWORD +} @Composable -fun LoginPage(onLoggedIn: (String) -> Unit) { +fun LoginScreen(onLoggedIn: (String) -> Unit) { val t = LocalTokens.current var email by remember { mutableStateOf("") } var password by remember { mutableStateOf("") } - val err by UiState.loginError.collectAsState() + val err by LoginUiState.loginError.collectAsState() DisposableEffect(Unit) { - onDispose { UiState.loginError.value = "" } + onDispose { LoginUiState.loginError.value = "" } } fun submit() { if (email.isBlank() || password.isEmpty()) { - UiState.loginError.value = "Enter email and password"; return + LoginUiState.loginError.value = "Enter email and password"; return } if (!checkCredentials(email, password)) { - UiState.loginError.value = "Invalid email or password"; return + LoginUiState.loginError.value = "Invalid email or password"; return } - UiState.loginError.value = "" + LoginUiState.loginError.value = "" val user = email.trim().lowercase() Repository.setSession(user) onLoggedIn(user) @@ -57,7 +69,7 @@ fun LoginPage(onLoggedIn: (String) -> Unit) { FieldLabel("Email") TextInput( value = email, - onChange = { email = it; UiState.loginError.value = "" }, + onChange = { email = it; LoginUiState.loginError.value = "" }, placeholder = DEMO_EMAIL, invalid = err.isNotEmpty(), keyboardType = KeyboardType.Email, @@ -69,7 +81,7 @@ fun LoginPage(onLoggedIn: (String) -> Unit) { FieldLabel("Password") TextInput( value = password, - onChange = { password = it; UiState.loginError.value = "" }, + onChange = { password = it; LoginUiState.loginError.value = "" }, placeholder = "••••••••", password = true, invalid = err.isNotEmpty(), diff --git a/examples/folio/composeApp/src/commonMain/kotlin/app/folio/feature/auth/LoginUiState.kt b/examples/folio/composeApp/src/commonMain/kotlin/app/folio/feature/auth/LoginUiState.kt new file mode 100644 index 0000000..e2c40e3 --- /dev/null +++ b/examples/folio/composeApp/src/commonMain/kotlin/app/folio/feature/auth/LoginUiState.kt @@ -0,0 +1,7 @@ +package app.folio.feature.auth + +import kotlinx.coroutines.flow.MutableStateFlow + +object LoginUiState { + val loginError = MutableStateFlow("") +} diff --git a/examples/sample-app/composeApp/src/commonMain/kotlin/dev/uatu/sample/ui/Home.kt b/examples/folio/composeApp/src/commonMain/kotlin/app/folio/feature/home/HomeScreen.kt similarity index 86% rename from examples/sample-app/composeApp/src/commonMain/kotlin/dev/uatu/sample/ui/Home.kt rename to examples/folio/composeApp/src/commonMain/kotlin/app/folio/feature/home/HomeScreen.kt index 88107c6..cbe68bc 100644 --- a/examples/sample-app/composeApp/src/commonMain/kotlin/dev/uatu/sample/ui/Home.kt +++ b/examples/folio/composeApp/src/commonMain/kotlin/app/folio/feature/home/HomeScreen.kt @@ -1,4 +1,4 @@ -package dev.uatu.sample.ui +package app.folio.feature.home import androidx.compose.foundation.background import androidx.compose.foundation.border @@ -23,16 +23,26 @@ import androidx.compose.ui.semantics.semantics import androidx.compose.ui.text.style.TextOverflow import androidx.compose.ui.unit.dp import androidx.compose.runtime.collectAsState -import dev.uatu.sample.Navigator -import dev.uatu.sample.Repository -import dev.uatu.sample.Route -import dev.uatu.sample.balanceOf -import dev.uatu.sample.formatCents -import dev.uatu.sample.initialsOf -import dev.uatu.sample.signedAmount +import app.folio.data.Repository +import app.folio.navigation.Navigator +import app.folio.navigation.Route +import app.folio.platform.balanceOf +import app.folio.platform.formatCents +import app.folio.platform.initialsOf +import app.folio.platform.signedAmount +import app.folio.ui.component.AppButton +import app.folio.ui.component.ButtonStyle +import app.folio.ui.component.EmptyState +import app.folio.ui.component.Header +import app.folio.ui.component.IconButton +import app.folio.ui.component.Screen +import app.folio.ui.icon.Icons +import app.folio.ui.theme.LocalTokens +import app.folio.ui.theme.RadiusLg +import app.folio.ui.theme.Type @Composable -fun HomePage(user: String, onLogout: () -> Unit) { +fun HomeScreen(user: String, onLogout: () -> Unit) { val t = LocalTokens.current val accounts by Repository.accounts.collectAsState() val txns by Repository.transactions.collectAsState() diff --git a/examples/sample-app/composeApp/src/commonMain/kotlin/dev/uatu/sample/ui/AddTransaction.kt b/examples/folio/composeApp/src/commonMain/kotlin/app/folio/feature/ledger/AddTransactionScreen.kt similarity index 73% rename from examples/sample-app/composeApp/src/commonMain/kotlin/dev/uatu/sample/ui/AddTransaction.kt rename to examples/folio/composeApp/src/commonMain/kotlin/app/folio/feature/ledger/AddTransactionScreen.kt index ec89ae3..cef4b05 100644 --- a/examples/sample-app/composeApp/src/commonMain/kotlin/dev/uatu/sample/ui/AddTransaction.kt +++ b/examples/folio/composeApp/src/commonMain/kotlin/app/folio/feature/ledger/AddTransactionScreen.kt @@ -1,4 +1,4 @@ -package dev.uatu.sample.ui +package app.folio.feature.ledger import androidx.compose.foundation.layout.Arrangement import androidx.compose.foundation.layout.Column @@ -15,17 +15,28 @@ import androidx.compose.ui.Modifier import androidx.compose.ui.text.input.KeyboardType import androidx.compose.ui.text.style.TextAlign import androidx.compose.ui.unit.dp -import dev.uatu.sample.Navigator -import dev.uatu.sample.Repository -import dev.uatu.sample.Route -import dev.uatu.sample.TxnType -import dev.uatu.sample.UiState -import dev.uatu.sample.parseCents +import app.folio.data.Repository +import app.folio.data.TxnType +import app.folio.navigation.Navigator +import app.folio.navigation.Route +import app.folio.platform.parseCents +import app.folio.ui.BackHandler +import app.folio.ui.component.AppButton +import app.folio.ui.component.BackButton +import app.folio.ui.component.ButtonStyle +import app.folio.ui.component.EmptyState +import app.folio.ui.component.ErrorText +import app.folio.ui.component.FieldLabel +import app.folio.ui.component.Header +import app.folio.ui.component.Screen +import app.folio.ui.component.Segmented +import app.folio.ui.component.TextInput +import app.folio.ui.theme.Type private val AMOUNT_REGEX = Regex("""^\d*(\.\d{0,2})?$""") @Composable -fun AddTransactionPage(accountId: String) { +fun AddTransactionScreen(accountId: String) { val accounts by Repository.accounts.collectAsState() val account = accounts.firstOrNull { it.id == accountId } @@ -50,31 +61,31 @@ fun AddTransactionPage(accountId: String) { var type by remember { mutableStateOf(TxnType.credit) } var amount by remember { mutableStateOf("") } var note by remember { mutableStateOf("") } - val err by UiState.txnError.collectAsState() + val err by AddTransactionUiState.txnError.collectAsState() DisposableEffect(type) { - UiState.txnFormType.value = if (type == TxnType.credit) "credit" else "debit" - onDispose { UiState.txnFormType.value = null } + AddTransactionUiState.txnFormType.value = if (type == TxnType.credit) "credit" else "debit" + onDispose { AddTransactionUiState.txnFormType.value = null } } DisposableEffect(Unit) { - onDispose { UiState.txnError.value = "" } + onDispose { AddTransactionUiState.txnError.value = "" } } fun submit() { val cents = parseCents(amount) if (cents == null) { - UiState.txnError.value = "Enter a valid amount (e.g. 12.34)"; return + AddTransactionUiState.txnError.value = "Enter a valid amount (e.g. 12.34)"; return } if (cents <= 0) { - UiState.txnError.value = "Amount must be greater than zero"; return + AddTransactionUiState.txnError.value = "Amount must be greater than zero"; return } try { Repository.createTransaction(accountId, type, cents, note) - UiState.txnError.value = "" + AddTransactionUiState.txnError.value = "" Navigator.back(Route.Home) } catch (e: IllegalArgumentException) { - UiState.txnError.value = e.message ?: "Could not save transaction" + AddTransactionUiState.txnError.value = e.message ?: "Could not save transaction" } } @@ -104,7 +115,7 @@ fun AddTransactionPage(accountId: String) { selected = if (type == TxnType.credit) 0 else 1, labels = listOf("Credit", "Debit"), onSelect = { - UiState.txnError.value = "" + AddTransactionUiState.txnError.value = "" type = if (it == 0) TxnType.credit else TxnType.debit }, descriptions = listOf("txn_credit", "txn_debit"), @@ -114,7 +125,7 @@ fun AddTransactionPage(accountId: String) { TextInput( value = amount, onChange = { - UiState.txnError.value = "" + AddTransactionUiState.txnError.value = "" if (AMOUNT_REGEX.matches(it) || it.isEmpty()) { amount = it } @@ -133,7 +144,7 @@ fun AddTransactionPage(accountId: String) { TextInput( value = note, onChange = { - UiState.txnError.value = "" + AddTransactionUiState.txnError.value = "" note = it.take(80) }, placeholder = "What's this for?", diff --git a/examples/sample-app/composeApp/src/commonMain/kotlin/dev/uatu/sample/UiState.kt b/examples/folio/composeApp/src/commonMain/kotlin/app/folio/feature/ledger/AddTransactionUiState.kt similarity index 52% rename from examples/sample-app/composeApp/src/commonMain/kotlin/dev/uatu/sample/UiState.kt rename to examples/folio/composeApp/src/commonMain/kotlin/app/folio/feature/ledger/AddTransactionUiState.kt index 39a77a9..1c6289f 100644 --- a/examples/sample-app/composeApp/src/commonMain/kotlin/dev/uatu/sample/UiState.kt +++ b/examples/folio/composeApp/src/commonMain/kotlin/app/folio/feature/ledger/AddTransactionUiState.kt @@ -1,10 +1,8 @@ -package dev.uatu.sample +package app.folio.feature.ledger import kotlinx.coroutines.flow.MutableStateFlow -object UiState { - val loginError = MutableStateFlow("") - val addAccountError = MutableStateFlow("") +object AddTransactionUiState { val txnError = MutableStateFlow("") val txnFormType = MutableStateFlow<String?>(null) } diff --git a/examples/sample-app/composeApp/src/commonMain/kotlin/dev/uatu/sample/ui/Ledger.kt b/examples/folio/composeApp/src/commonMain/kotlin/app/folio/feature/ledger/LedgerScreen.kt similarity index 86% rename from examples/sample-app/composeApp/src/commonMain/kotlin/dev/uatu/sample/ui/Ledger.kt rename to examples/folio/composeApp/src/commonMain/kotlin/app/folio/feature/ledger/LedgerScreen.kt index dd6f4f0..4f34905 100644 --- a/examples/sample-app/composeApp/src/commonMain/kotlin/dev/uatu/sample/ui/Ledger.kt +++ b/examples/folio/composeApp/src/commonMain/kotlin/app/folio/feature/ledger/LedgerScreen.kt @@ -1,4 +1,4 @@ -package dev.uatu.sample.ui +package app.folio.feature.ledger import androidx.compose.foundation.background import androidx.compose.foundation.border @@ -22,17 +22,29 @@ import androidx.compose.ui.semantics.contentDescription import androidx.compose.ui.semantics.semantics import androidx.compose.ui.text.style.TextOverflow import androidx.compose.ui.unit.dp -import dev.uatu.sample.Navigator -import dev.uatu.sample.Repository -import dev.uatu.sample.Route -import dev.uatu.sample.TxnType -import dev.uatu.sample.balanceOf -import dev.uatu.sample.formatCents -import dev.uatu.sample.formatDate -import dev.uatu.sample.signedAmount +import app.folio.data.Repository +import app.folio.navigation.Navigator +import app.folio.navigation.Route +import app.folio.data.TxnType +import app.folio.platform.balanceOf +import app.folio.platform.formatCents +import app.folio.platform.formatDate +import app.folio.platform.signedAmount +import app.folio.ui.BackHandler +import app.folio.ui.component.AppButton +import app.folio.ui.component.BackButton +import app.folio.ui.component.ButtonStyle +import app.folio.ui.component.Card +import app.folio.ui.component.EmptyState +import app.folio.ui.component.Header +import app.folio.ui.component.Screen +import app.folio.ui.icon.Icons +import app.folio.ui.theme.LocalTokens +import app.folio.ui.theme.RadiusLg +import app.folio.ui.theme.Type @Composable -fun LedgerPage(accountId: String) { +fun LedgerScreen(accountId: String) { val t = LocalTokens.current val accounts by Repository.accounts.collectAsState() val allTxns by Repository.transactions.collectAsState() diff --git a/examples/sample-app/composeApp/src/commonMain/kotlin/dev/uatu/sample/Navigation.kt b/examples/folio/composeApp/src/commonMain/kotlin/app/folio/navigation/Navigator.kt similarity index 74% rename from examples/sample-app/composeApp/src/commonMain/kotlin/dev/uatu/sample/Navigation.kt rename to examples/folio/composeApp/src/commonMain/kotlin/app/folio/navigation/Navigator.kt index 12db5d3..841dc00 100644 --- a/examples/sample-app/composeApp/src/commonMain/kotlin/dev/uatu/sample/Navigation.kt +++ b/examples/folio/composeApp/src/commonMain/kotlin/app/folio/navigation/Navigator.kt @@ -1,17 +1,9 @@ -package dev.uatu.sample +package app.folio.navigation import kotlinx.coroutines.flow.MutableStateFlow import kotlinx.coroutines.flow.StateFlow import kotlinx.coroutines.flow.asStateFlow -sealed interface Route { - data object Login : Route - data object Home : Route - data object AddAccount : Route - data class Ledger(val accountId: String) : Route - data class AddTransaction(val accountId: String) : Route -} - object Navigator { private val stack = ArrayDeque<Route>().apply { addLast(Route.Home) } private val _current = MutableStateFlow<Route>(Route.Home) diff --git a/examples/folio/composeApp/src/commonMain/kotlin/app/folio/navigation/Route.kt b/examples/folio/composeApp/src/commonMain/kotlin/app/folio/navigation/Route.kt new file mode 100644 index 0000000..cb8a6e3 --- /dev/null +++ b/examples/folio/composeApp/src/commonMain/kotlin/app/folio/navigation/Route.kt @@ -0,0 +1,9 @@ +package app.folio.navigation + +sealed interface Route { + data object Login : Route + data object Home : Route + data object AddAccount : Route + data class Ledger(val accountId: String) : Route + data class AddTransaction(val accountId: String) : Route +} diff --git a/examples/sample-app/composeApp/src/commonMain/kotlin/dev/uatu/sample/Format.kt b/examples/folio/composeApp/src/commonMain/kotlin/app/folio/platform/Format.kt similarity index 95% rename from examples/sample-app/composeApp/src/commonMain/kotlin/dev/uatu/sample/Format.kt rename to examples/folio/composeApp/src/commonMain/kotlin/app/folio/platform/Format.kt index 9e691b2..efbb4d1 100644 --- a/examples/sample-app/composeApp/src/commonMain/kotlin/dev/uatu/sample/Format.kt +++ b/examples/folio/composeApp/src/commonMain/kotlin/app/folio/platform/Format.kt @@ -1,5 +1,7 @@ -package dev.uatu.sample +package app.folio.platform +import app.folio.data.Transaction +import app.folio.data.TxnType import kotlin.math.absoluteValue private const val CURRENCY = "$" diff --git a/examples/sample-app/composeApp/src/commonMain/kotlin/dev/uatu/sample/Platform.kt b/examples/folio/composeApp/src/commonMain/kotlin/app/folio/platform/Platform.kt similarity index 73% rename from examples/sample-app/composeApp/src/commonMain/kotlin/dev/uatu/sample/Platform.kt rename to examples/folio/composeApp/src/commonMain/kotlin/app/folio/platform/Platform.kt index 608e13a..dc4e3ce 100644 --- a/examples/sample-app/composeApp/src/commonMain/kotlin/dev/uatu/sample/Platform.kt +++ b/examples/folio/composeApp/src/commonMain/kotlin/app/folio/platform/Platform.kt @@ -1,4 +1,4 @@ -package dev.uatu.sample +package app.folio.platform expect object Platform { fun now(): Long diff --git a/examples/sample-app/composeApp/src/commonMain/kotlin/dev/uatu/sample/ui/BackHandler.kt b/examples/folio/composeApp/src/commonMain/kotlin/app/folio/ui/BackHandler.kt similarity index 78% rename from examples/sample-app/composeApp/src/commonMain/kotlin/dev/uatu/sample/ui/BackHandler.kt rename to examples/folio/composeApp/src/commonMain/kotlin/app/folio/ui/BackHandler.kt index 830118e..47044cf 100644 --- a/examples/sample-app/composeApp/src/commonMain/kotlin/dev/uatu/sample/ui/BackHandler.kt +++ b/examples/folio/composeApp/src/commonMain/kotlin/app/folio/ui/BackHandler.kt @@ -1,4 +1,4 @@ -package dev.uatu.sample.ui +package app.folio.ui import androidx.compose.runtime.Composable diff --git a/examples/folio/composeApp/src/commonMain/kotlin/app/folio/ui/component/AppButton.kt b/examples/folio/composeApp/src/commonMain/kotlin/app/folio/ui/component/AppButton.kt new file mode 100644 index 0000000..d6012e4 --- /dev/null +++ b/examples/folio/composeApp/src/commonMain/kotlin/app/folio/ui/component/AppButton.kt @@ -0,0 +1,57 @@ +package app.folio.ui.component + +import androidx.compose.foundation.BorderStroke +import androidx.compose.foundation.background +import androidx.compose.foundation.border +import androidx.compose.foundation.clickable +import androidx.compose.foundation.layout.Box +import androidx.compose.foundation.layout.fillMaxWidth +import androidx.compose.foundation.layout.padding +import androidx.compose.foundation.shape.RoundedCornerShape +import androidx.compose.material3.Text +import androidx.compose.runtime.Composable +import androidx.compose.ui.Alignment +import androidx.compose.ui.Modifier +import androidx.compose.ui.draw.clip +import androidx.compose.ui.semantics.Role +import androidx.compose.ui.semantics.contentDescription +import androidx.compose.ui.semantics.semantics +import androidx.compose.ui.unit.dp +import app.folio.ui.theme.LocalTokens +import app.folio.ui.theme.RadiusMd +import app.folio.ui.theme.Type + +enum class ButtonStyle { Primary, Secondary, Ghost } + +@Composable +fun AppButton( + text: String, + onClick: () -> Unit, + modifier: Modifier = Modifier, + style: ButtonStyle = ButtonStyle.Secondary, + enabled: Boolean = true, + description: String? = null, +) { + val t = LocalTokens.current + val (bg, fg, border) = when (style) { + ButtonStyle.Primary -> Triple(t.text, t.onAccent, t.text) + ButtonStyle.Secondary -> Triple(t.surface2, t.text, t.borderStrong) + ButtonStyle.Ghost -> Triple(t.bg, t.textMuted, t.bg) + } + Box( + modifier = modifier + .fillMaxWidth() + .clip(RoundedCornerShape(RadiusMd)) + .background(if (enabled) bg else t.surface3) + .border(BorderStroke(1.dp, if (enabled) border else t.border), RoundedCornerShape(RadiusMd)) + .then( + if (description != null) Modifier.semantics { contentDescription = description } + else Modifier + ) + .clickable(enabled = enabled, role = Role.Button, onClick = onClick) + .padding(vertical = 14.dp, horizontal = 16.dp), + contentAlignment = Alignment.Center, + ) { + Text(text, style = Type.button, color = if (enabled) fg else t.textFaint) + } +} diff --git a/examples/folio/composeApp/src/commonMain/kotlin/app/folio/ui/component/Card.kt b/examples/folio/composeApp/src/commonMain/kotlin/app/folio/ui/component/Card.kt new file mode 100644 index 0000000..236633b --- /dev/null +++ b/examples/folio/composeApp/src/commonMain/kotlin/app/folio/ui/component/Card.kt @@ -0,0 +1,33 @@ +package app.folio.ui.component + +import androidx.compose.foundation.background +import androidx.compose.foundation.border +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.foundation.shape.RoundedCornerShape +import androidx.compose.runtime.Composable +import androidx.compose.ui.Modifier +import androidx.compose.ui.draw.clip +import androidx.compose.ui.unit.dp +import app.folio.ui.theme.LocalTokens +import app.folio.ui.theme.RadiusLg + +@Composable +fun Card( + modifier: Modifier = Modifier, + dashed: Boolean = false, + content: @Composable () -> Unit, +) { + val t = LocalTokens.current + Column( + modifier = modifier + .fillMaxWidth() + .clip(RoundedCornerShape(RadiusLg)) + .background(if (dashed) t.surface2 else t.surface) + .border(1.dp, t.border, RoundedCornerShape(RadiusLg)) + .padding(16.dp), + verticalArrangement = Arrangement.spacedBy(6.dp), + ) { content() } +} diff --git a/examples/folio/composeApp/src/commonMain/kotlin/app/folio/ui/component/EmptyState.kt b/examples/folio/composeApp/src/commonMain/kotlin/app/folio/ui/component/EmptyState.kt new file mode 100644 index 0000000..69d52c8 --- /dev/null +++ b/examples/folio/composeApp/src/commonMain/kotlin/app/folio/ui/component/EmptyState.kt @@ -0,0 +1,56 @@ +package app.folio.ui.component + +import androidx.compose.foundation.background +import androidx.compose.foundation.border +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.fillMaxWidth +import androidx.compose.foundation.layout.height +import androidx.compose.foundation.layout.padding +import androidx.compose.foundation.layout.size +import androidx.compose.foundation.shape.CircleShape +import androidx.compose.material3.Icon +import androidx.compose.material3.Text +import androidx.compose.runtime.Composable +import androidx.compose.ui.Alignment +import androidx.compose.ui.Modifier +import androidx.compose.ui.graphics.vector.ImageVector +import androidx.compose.ui.graphics.vector.rememberVectorPainter +import androidx.compose.ui.text.style.TextAlign +import androidx.compose.ui.unit.dp +import app.folio.ui.theme.LocalTokens +import app.folio.ui.theme.Type + +@Composable +fun EmptyState(title: String, subtitle: String, icon: ImageVector? = null) { + val t = LocalTokens.current + Column( + modifier = Modifier + .fillMaxWidth() + .padding(vertical = 48.dp, horizontal = 16.dp), + horizontalAlignment = Alignment.CenterHorizontally, + verticalArrangement = Arrangement.spacedBy(6.dp), + ) { + if (icon != null) { + Box( + modifier = Modifier + .size(56.dp) + .background(t.surface2, CircleShape) + .border(1.dp, t.border, CircleShape), + contentAlignment = Alignment.Center, + ) { + Icon( + painter = rememberVectorPainter(icon), + contentDescription = null, + tint = t.textMuted, + modifier = Modifier.size(22.dp), + ) + } + Spacer(Modifier.height(2.dp)) + } + Text(title, style = Type.bodyStrong, color = t.text, textAlign = TextAlign.Center) + Text(subtitle, style = Type.caption, color = t.textMuted, textAlign = TextAlign.Center) + } +} diff --git a/examples/folio/composeApp/src/commonMain/kotlin/app/folio/ui/component/ErrorText.kt b/examples/folio/composeApp/src/commonMain/kotlin/app/folio/ui/component/ErrorText.kt new file mode 100644 index 0000000..c9322be --- /dev/null +++ b/examples/folio/composeApp/src/commonMain/kotlin/app/folio/ui/component/ErrorText.kt @@ -0,0 +1,21 @@ +package app.folio.ui.component + +import androidx.compose.foundation.layout.fillMaxWidth +import androidx.compose.foundation.layout.height +import androidx.compose.material3.Text +import androidx.compose.runtime.Composable +import androidx.compose.ui.Modifier +import androidx.compose.ui.unit.dp +import app.folio.ui.theme.LocalTokens +import app.folio.ui.theme.Type + +@Composable +fun ErrorText(err: String?) { + val t = LocalTokens.current + Text( + err ?: "", + style = Type.caption, + color = t.text, + modifier = Modifier.fillMaxWidth().height(18.dp), + ) +} diff --git a/examples/folio/composeApp/src/commonMain/kotlin/app/folio/ui/component/FieldLabel.kt b/examples/folio/composeApp/src/commonMain/kotlin/app/folio/ui/component/FieldLabel.kt new file mode 100644 index 0000000..ce6ea55 --- /dev/null +++ b/examples/folio/composeApp/src/commonMain/kotlin/app/folio/ui/component/FieldLabel.kt @@ -0,0 +1,12 @@ +package app.folio.ui.component + +import androidx.compose.material3.Text +import androidx.compose.runtime.Composable +import app.folio.ui.theme.LocalTokens +import app.folio.ui.theme.Type + +@Composable +fun FieldLabel(text: String) { + val t = LocalTokens.current + Text(text.uppercase(), style = Type.label, color = t.textMuted) +} diff --git a/examples/folio/composeApp/src/commonMain/kotlin/app/folio/ui/component/Header.kt b/examples/folio/composeApp/src/commonMain/kotlin/app/folio/ui/component/Header.kt new file mode 100644 index 0000000..58af281 --- /dev/null +++ b/examples/folio/composeApp/src/commonMain/kotlin/app/folio/ui/component/Header.kt @@ -0,0 +1,48 @@ +package app.folio.ui.component + +import androidx.compose.foundation.layout.Arrangement +import androidx.compose.foundation.layout.Column +import androidx.compose.foundation.layout.Row +import androidx.compose.foundation.layout.fillMaxWidth +import androidx.compose.foundation.layout.padding +import androidx.compose.material3.Text +import androidx.compose.runtime.Composable +import androidx.compose.ui.Alignment +import androidx.compose.ui.Modifier +import androidx.compose.ui.text.style.TextOverflow +import androidx.compose.ui.unit.dp +import app.folio.ui.theme.LocalTokens +import app.folio.ui.theme.ScreenPad +import app.folio.ui.theme.Type + +@Composable +fun Header( + title: String, + subtitle: String? = null, + left: @Composable (() -> Unit)? = null, + right: @Composable (() -> Unit)? = null, +) { + val t = LocalTokens.current + Row( + modifier = Modifier + .fillMaxWidth() + .padding(start = ScreenPad, end = ScreenPad, top = 14.dp, bottom = 8.dp), + verticalAlignment = Alignment.CenterVertically, + horizontalArrangement = Arrangement.spacedBy(12.dp), + ) { + if (left != null) left() + Column(Modifier.weight(1f)) { + Text( + title, + style = Type.title, + color = t.text, + maxLines = 1, + overflow = TextOverflow.Ellipsis, + ) + if (subtitle != null) { + Text(subtitle, style = Type.caption, color = t.textMuted) + } + } + if (right != null) right() + } +} diff --git a/examples/folio/composeApp/src/commonMain/kotlin/app/folio/ui/component/IconButton.kt b/examples/folio/composeApp/src/commonMain/kotlin/app/folio/ui/component/IconButton.kt new file mode 100644 index 0000000..81624e7 --- /dev/null +++ b/examples/folio/composeApp/src/commonMain/kotlin/app/folio/ui/component/IconButton.kt @@ -0,0 +1,51 @@ +package app.folio.ui.component + +import androidx.compose.foundation.background +import androidx.compose.foundation.border +import androidx.compose.foundation.clickable +import androidx.compose.foundation.layout.Box +import androidx.compose.foundation.layout.size +import androidx.compose.foundation.shape.RoundedCornerShape +import androidx.compose.material3.Icon +import androidx.compose.runtime.Composable +import androidx.compose.ui.Alignment +import androidx.compose.ui.Modifier +import androidx.compose.ui.draw.clip +import androidx.compose.ui.graphics.vector.ImageVector +import androidx.compose.ui.graphics.vector.rememberVectorPainter +import androidx.compose.ui.semantics.contentDescription +import androidx.compose.ui.semantics.semantics +import androidx.compose.ui.unit.dp +import app.folio.ui.icon.Icons +import app.folio.ui.theme.LocalTokens + +@Composable +fun IconButton( + onClick: () -> Unit, + description: String, + icon: ImageVector, +) { + val t = LocalTokens.current + Box( + modifier = Modifier + .size(36.dp) + .clip(RoundedCornerShape(10.dp)) + .background(t.surface2) + .border(1.dp, t.border, RoundedCornerShape(10.dp)) + .clickable(onClick = onClick) + .semantics { contentDescription = description }, + contentAlignment = Alignment.Center, + ) { + Icon( + painter = rememberVectorPainter(icon), + contentDescription = null, + tint = t.text, + modifier = Modifier.size(16.dp), + ) + } +} + +@Composable +fun BackButton(onClick: () -> Unit) { + IconButton(onClick = onClick, description = "Back", icon = Icons.Back) +} diff --git a/examples/folio/composeApp/src/commonMain/kotlin/app/folio/ui/component/Screen.kt b/examples/folio/composeApp/src/commonMain/kotlin/app/folio/ui/component/Screen.kt new file mode 100644 index 0000000..222e56b --- /dev/null +++ b/examples/folio/composeApp/src/commonMain/kotlin/app/folio/ui/component/Screen.kt @@ -0,0 +1,53 @@ +package app.folio.ui.component + +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.Spacer +import androidx.compose.foundation.layout.fillMaxSize +import androidx.compose.foundation.layout.fillMaxWidth +import androidx.compose.foundation.layout.height +import androidx.compose.foundation.layout.padding +import androidx.compose.foundation.rememberScrollState +import androidx.compose.foundation.verticalScroll +import androidx.compose.runtime.Composable +import androidx.compose.ui.Modifier +import androidx.compose.ui.unit.dp +import app.folio.ui.theme.LocalTokens +import app.folio.ui.theme.ScreenPad + +@Composable +fun Screen( + header: @Composable (() -> Unit)? = null, + footer: @Composable (() -> Unit)? = null, + content: @Composable () -> Unit, +) { + val t = LocalTokens.current + Column(Modifier.fillMaxSize()) { + if (header != null) header() + Column( + modifier = Modifier + .weight(1f) + .fillMaxWidth() + .verticalScroll(rememberScrollState()) + .padding(horizontal = ScreenPad, vertical = 8.dp), + verticalArrangement = Arrangement.spacedBy(12.dp), + ) { + content() + Spacer(Modifier.height(16.dp)) + } + if (footer != null) { + Box(Modifier.fillMaxWidth().height(1.dp).background(t.border)) + Column( + modifier = Modifier + .fillMaxWidth() + .background(t.bg) + .padding(horizontal = ScreenPad, vertical = 16.dp), + verticalArrangement = Arrangement.spacedBy(14.dp), + ) { + footer() + } + } + } +} diff --git a/examples/folio/composeApp/src/commonMain/kotlin/app/folio/ui/component/Segmented.kt b/examples/folio/composeApp/src/commonMain/kotlin/app/folio/ui/component/Segmented.kt new file mode 100644 index 0000000..89a1ea3 --- /dev/null +++ b/examples/folio/composeApp/src/commonMain/kotlin/app/folio/ui/component/Segmented.kt @@ -0,0 +1,64 @@ +package app.folio.ui.component + +import androidx.compose.foundation.background +import androidx.compose.foundation.border +import androidx.compose.foundation.clickable +import androidx.compose.foundation.layout.Arrangement +import androidx.compose.foundation.layout.Box +import androidx.compose.foundation.layout.Row +import androidx.compose.foundation.layout.fillMaxWidth +import androidx.compose.foundation.layout.padding +import androidx.compose.foundation.shape.RoundedCornerShape +import androidx.compose.material3.Text +import androidx.compose.runtime.Composable +import androidx.compose.ui.Alignment +import androidx.compose.ui.Modifier +import androidx.compose.ui.draw.clip +import androidx.compose.ui.semantics.Role +import androidx.compose.ui.semantics.contentDescription +import androidx.compose.ui.semantics.selected +import androidx.compose.ui.semantics.semantics +import androidx.compose.ui.unit.dp +import app.folio.ui.theme.LocalTokens +import app.folio.ui.theme.RadiusMd +import app.folio.ui.theme.RadiusSm +import app.folio.ui.theme.Type + +@Composable +fun Segmented( + selected: Int, + labels: List<String>, + onSelect: (Int) -> Unit, + descriptions: List<String>? = null, +) { + val t = LocalTokens.current + Row( + modifier = Modifier + .fillMaxWidth() + .clip(RoundedCornerShape(RadiusMd)) + .background(t.surface) + .border(1.dp, t.border, RoundedCornerShape(RadiusMd)) + .padding(4.dp), + horizontalArrangement = Arrangement.spacedBy(4.dp), + ) { + labels.forEachIndexed { i, label -> + val active = i == selected + val desc = descriptions?.getOrNull(i) + Box( + modifier = Modifier + .weight(1f) + .clip(RoundedCornerShape(RadiusSm)) + .background(if (active) t.surface3 else t.surface) + .semantics { + this.selected = active + if (desc != null) contentDescription = desc + } + .clickable(role = Role.Tab) { onSelect(i) } + .padding(vertical = 10.dp), + contentAlignment = Alignment.Center, + ) { + Text(label, style = Type.button, color = if (active) t.text else t.textMuted) + } + } + } +} diff --git a/examples/folio/composeApp/src/commonMain/kotlin/app/folio/ui/component/TextInput.kt b/examples/folio/composeApp/src/commonMain/kotlin/app/folio/ui/component/TextInput.kt new file mode 100644 index 0000000..a2ecdd3 --- /dev/null +++ b/examples/folio/composeApp/src/commonMain/kotlin/app/folio/ui/component/TextInput.kt @@ -0,0 +1,95 @@ +package app.folio.ui.component + +import androidx.compose.foundation.BorderStroke +import androidx.compose.foundation.background +import androidx.compose.foundation.border +import androidx.compose.foundation.layout.Box +import androidx.compose.foundation.layout.fillMaxWidth +import androidx.compose.foundation.layout.padding +import androidx.compose.foundation.shape.RoundedCornerShape +import androidx.compose.foundation.text.BasicTextField +import androidx.compose.foundation.text.KeyboardOptions +import androidx.compose.material3.Text +import androidx.compose.runtime.Composable +import androidx.compose.runtime.getValue +import androidx.compose.runtime.mutableStateOf +import androidx.compose.runtime.remember +import androidx.compose.runtime.setValue +import androidx.compose.ui.Alignment +import androidx.compose.ui.Modifier +import androidx.compose.ui.draw.clip +import androidx.compose.ui.focus.onFocusChanged +import androidx.compose.ui.graphics.SolidColor +import androidx.compose.ui.semantics.contentDescription +import androidx.compose.ui.semantics.semantics +import androidx.compose.ui.semantics.stateDescription +import androidx.compose.ui.text.TextStyle +import androidx.compose.ui.text.input.KeyboardType +import androidx.compose.ui.text.input.PasswordVisualTransformation +import androidx.compose.ui.text.input.VisualTransformation +import androidx.compose.ui.text.style.TextAlign +import androidx.compose.ui.unit.dp +import app.folio.FocusTracker +import app.folio.ui.theme.LocalTokens +import app.folio.ui.theme.RadiusMd +import app.folio.ui.theme.Type + +@Composable +fun TextInput( + value: String, + onChange: (String) -> Unit, + placeholder: String = "", + invalid: Boolean = false, + password: Boolean = false, + keyboardType: KeyboardType = KeyboardType.Text, + textAlign: TextAlign = TextAlign.Start, + textStyle: TextStyle = Type.body, + label: String? = null, + description: String? = null, + modifier: Modifier = Modifier, +) { + val t = LocalTokens.current + var focused by remember { mutableStateOf(false) } + val borderColor = if (focused || invalid) t.text else t.border + val bg = if (focused) t.surface2 else t.surface + Box( + modifier = modifier + .fillMaxWidth() + .clip(RoundedCornerShape(RadiusMd)) + .background(bg) + .border(BorderStroke(1.dp, borderColor), RoundedCornerShape(RadiusMd)) + .padding(horizontal = 14.dp, vertical = 14.dp), + contentAlignment = if (textAlign == TextAlign.Center) Alignment.Center else Alignment.CenterStart, + ) { + if (value.isEmpty() && placeholder.isNotEmpty()) { + Text( + placeholder, + style = textStyle.copy(color = t.textFaint, textAlign = textAlign), + modifier = Modifier.fillMaxWidth(), + ) + } + BasicTextField( + value = value, + onValueChange = onChange, + textStyle = textStyle.copy(color = t.text, textAlign = textAlign), + singleLine = true, + visualTransformation = if (password) PasswordVisualTransformation() else VisualTransformation.None, + keyboardOptions = KeyboardOptions(keyboardType = keyboardType), + cursorBrush = SolidColor(t.text), + modifier = Modifier + .fillMaxWidth() + .onFocusChanged { + focused = it.isFocused + if (description != null) { + if (it.isFocused) FocusTracker.enter(description) + else FocusTracker.leave(description) + } + } + .semantics { + val desc = description ?: label + if (desc != null) contentDescription = desc + if (invalid) stateDescription = "Invalid" + }, + ) + } +} diff --git a/examples/sample-app/composeApp/src/commonMain/kotlin/dev/uatu/sample/ui/Icons.kt b/examples/folio/composeApp/src/commonMain/kotlin/app/folio/ui/icon/Icons.kt similarity index 97% rename from examples/sample-app/composeApp/src/commonMain/kotlin/dev/uatu/sample/ui/Icons.kt rename to examples/folio/composeApp/src/commonMain/kotlin/app/folio/ui/icon/Icons.kt index de35f36..2f1b183 100644 --- a/examples/sample-app/composeApp/src/commonMain/kotlin/dev/uatu/sample/ui/Icons.kt +++ b/examples/folio/composeApp/src/commonMain/kotlin/app/folio/ui/icon/Icons.kt @@ -1,4 +1,4 @@ -package dev.uatu.sample.ui +package app.folio.ui.icon import androidx.compose.ui.graphics.Color import androidx.compose.ui.graphics.SolidColor diff --git a/examples/sample-app/composeApp/src/commonMain/kotlin/dev/uatu/sample/ui/Theme.kt b/examples/folio/composeApp/src/commonMain/kotlin/app/folio/ui/theme/Theme.kt similarity index 52% rename from examples/sample-app/composeApp/src/commonMain/kotlin/dev/uatu/sample/ui/Theme.kt rename to examples/folio/composeApp/src/commonMain/kotlin/app/folio/ui/theme/Theme.kt index 3a12b22..0cc1ef7 100644 --- a/examples/sample-app/composeApp/src/commonMain/kotlin/dev/uatu/sample/ui/Theme.kt +++ b/examples/folio/composeApp/src/commonMain/kotlin/app/folio/ui/theme/Theme.kt @@ -1,4 +1,4 @@ -package dev.uatu.sample.ui +package app.folio.ui.theme import androidx.compose.foundation.isSystemInDarkTheme import androidx.compose.material3.MaterialTheme @@ -6,11 +6,7 @@ import androidx.compose.material3.lightColorScheme import androidx.compose.runtime.Composable import androidx.compose.runtime.staticCompositionLocalOf import androidx.compose.ui.graphics.Color -import androidx.compose.ui.text.TextStyle -import androidx.compose.ui.text.font.FontFamily -import androidx.compose.ui.text.font.FontWeight import androidx.compose.ui.unit.dp -import androidx.compose.ui.unit.sp data class Tokens( val bg: Color = Color(0xFFFFFFFF), @@ -27,20 +23,6 @@ data class Tokens( val LocalTokens = staticCompositionLocalOf { Tokens() } -val Mono: FontFamily = FontFamily.Monospace - -object Type { - val body = TextStyle(fontFamily = Mono, fontSize = 15.sp, fontWeight = FontWeight.Normal) - val bodyStrong = TextStyle(fontFamily = Mono, fontSize = 15.sp, fontWeight = FontWeight.SemiBold) - val title = TextStyle(fontFamily = Mono, fontSize = 20.sp, fontWeight = FontWeight.Bold, letterSpacing = (-0.2).sp) - val caption = TextStyle(fontFamily = Mono, fontSize = 12.sp, fontWeight = FontWeight.Normal) - val label = TextStyle(fontFamily = Mono, fontSize = 12.sp, fontWeight = FontWeight.Normal, letterSpacing = 0.7.sp) - val balance = TextStyle(fontFamily = Mono, fontSize = 28.sp, fontWeight = FontWeight.Bold, letterSpacing = (-0.3).sp) - val amountInput = TextStyle(fontFamily = Mono, fontSize = 36.sp, fontWeight = FontWeight.Bold, letterSpacing = (-0.3).sp) - val button = TextStyle(fontFamily = Mono, fontSize = 15.sp, fontWeight = FontWeight.SemiBold) - val brand = TextStyle(fontFamily = Mono, fontSize = 22.sp, fontWeight = FontWeight.Bold, letterSpacing = (-0.2).sp) -} - @Composable fun LedgerTheme(content: @Composable () -> Unit) { @Suppress("UNUSED_VARIABLE") diff --git a/examples/folio/composeApp/src/commonMain/kotlin/app/folio/ui/theme/Type.kt b/examples/folio/composeApp/src/commonMain/kotlin/app/folio/ui/theme/Type.kt new file mode 100644 index 0000000..44ccbd8 --- /dev/null +++ b/examples/folio/composeApp/src/commonMain/kotlin/app/folio/ui/theme/Type.kt @@ -0,0 +1,20 @@ +package app.folio.ui.theme + +import androidx.compose.ui.text.TextStyle +import androidx.compose.ui.text.font.FontFamily +import androidx.compose.ui.text.font.FontWeight +import androidx.compose.ui.unit.sp + +val Mono: FontFamily = FontFamily.Monospace + +object Type { + val body = TextStyle(fontFamily = Mono, fontSize = 15.sp, fontWeight = FontWeight.Normal) + val bodyStrong = TextStyle(fontFamily = Mono, fontSize = 15.sp, fontWeight = FontWeight.SemiBold) + val title = TextStyle(fontFamily = Mono, fontSize = 20.sp, fontWeight = FontWeight.Bold, letterSpacing = (-0.2).sp) + val caption = TextStyle(fontFamily = Mono, fontSize = 12.sp, fontWeight = FontWeight.Normal) + val label = TextStyle(fontFamily = Mono, fontSize = 12.sp, fontWeight = FontWeight.Normal, letterSpacing = 0.7.sp) + val balance = TextStyle(fontFamily = Mono, fontSize = 28.sp, fontWeight = FontWeight.Bold, letterSpacing = (-0.3).sp) + val amountInput = TextStyle(fontFamily = Mono, fontSize = 36.sp, fontWeight = FontWeight.Bold, letterSpacing = (-0.3).sp) + val button = TextStyle(fontFamily = Mono, fontSize = 15.sp, fontWeight = FontWeight.SemiBold) + val brand = TextStyle(fontFamily = Mono, fontSize = 22.sp, fontWeight = FontWeight.Bold, letterSpacing = (-0.2).sp) +} diff --git a/examples/sample-app/composeApp/src/commonMain/sqldelight/dev/uatu/sample/db/Ledger.sq b/examples/folio/composeApp/src/commonMain/sqldelight/app/folio/db/Ledger.sq similarity index 100% rename from examples/sample-app/composeApp/src/commonMain/sqldelight/dev/uatu/sample/db/Ledger.sq rename to examples/folio/composeApp/src/commonMain/sqldelight/app/folio/db/Ledger.sq diff --git a/examples/sample-app/composeApp/src/iosMain/kotlin/dev/uatu/sample/MainViewController.kt b/examples/folio/composeApp/src/iosMain/kotlin/app/folio/MainViewController.kt similarity index 93% rename from examples/sample-app/composeApp/src/iosMain/kotlin/dev/uatu/sample/MainViewController.kt rename to examples/folio/composeApp/src/iosMain/kotlin/app/folio/MainViewController.kt index dd2e8df..bb8a853 100644 --- a/examples/sample-app/composeApp/src/iosMain/kotlin/dev/uatu/sample/MainViewController.kt +++ b/examples/folio/composeApp/src/iosMain/kotlin/app/folio/MainViewController.kt @@ -1,7 +1,8 @@ -package dev.uatu.sample +package app.folio import androidx.compose.ui.window.ComposeUIViewController -import dev.uatu.sample.ui.IosBackGesture +import app.folio.data.Repository +import app.folio.ui.IosBackGesture import kotlin.native.ObjCName import kotlinx.cinterop.BetaInteropApi import kotlinx.cinterop.ExperimentalForeignApi diff --git a/examples/sample-app/composeApp/src/iosMain/kotlin/dev/uatu/sample/DriverFactory.ios.kt b/examples/folio/composeApp/src/iosMain/kotlin/app/folio/data/DriverFactory.ios.kt similarity index 78% rename from examples/sample-app/composeApp/src/iosMain/kotlin/dev/uatu/sample/DriverFactory.ios.kt rename to examples/folio/composeApp/src/iosMain/kotlin/app/folio/data/DriverFactory.ios.kt index 1d366c1..598ccfb 100644 --- a/examples/sample-app/composeApp/src/iosMain/kotlin/dev/uatu/sample/DriverFactory.ios.kt +++ b/examples/folio/composeApp/src/iosMain/kotlin/app/folio/data/DriverFactory.ios.kt @@ -1,8 +1,8 @@ -package dev.uatu.sample +package app.folio.data import app.cash.sqldelight.db.SqlDriver import app.cash.sqldelight.driver.native.NativeSqliteDriver -import dev.uatu.sample.db.LedgerDatabase +import app.folio.db.LedgerDatabase actual class DriverFactory { actual fun create(): SqlDriver = diff --git a/examples/sample-app/composeApp/src/iosMain/kotlin/dev/uatu/sample/LedgerStore.ios.kt b/examples/folio/composeApp/src/iosMain/kotlin/app/folio/data/LedgerStore.ios.kt similarity index 76% rename from examples/sample-app/composeApp/src/iosMain/kotlin/dev/uatu/sample/LedgerStore.ios.kt rename to examples/folio/composeApp/src/iosMain/kotlin/app/folio/data/LedgerStore.ios.kt index 514f59a..594de08 100644 --- a/examples/sample-app/composeApp/src/iosMain/kotlin/dev/uatu/sample/LedgerStore.ios.kt +++ b/examples/folio/composeApp/src/iosMain/kotlin/app/folio/data/LedgerStore.ios.kt @@ -1,3 +1,3 @@ -package dev.uatu.sample +package app.folio.data actual fun createLedgerStore(): LedgerStore = SqlLedgerStore(DriverFactory()) diff --git a/examples/sample-app/composeApp/src/iosMain/kotlin/dev/uatu/sample/Format.ios.kt b/examples/folio/composeApp/src/iosMain/kotlin/app/folio/platform/Format.ios.kt similarity index 94% rename from examples/sample-app/composeApp/src/iosMain/kotlin/dev/uatu/sample/Format.ios.kt rename to examples/folio/composeApp/src/iosMain/kotlin/app/folio/platform/Format.ios.kt index 4877dc2..c78fc7b 100644 --- a/examples/sample-app/composeApp/src/iosMain/kotlin/dev/uatu/sample/Format.ios.kt +++ b/examples/folio/composeApp/src/iosMain/kotlin/app/folio/platform/Format.ios.kt @@ -1,4 +1,4 @@ -package dev.uatu.sample +package app.folio.platform import platform.Foundation.NSDate import platform.Foundation.NSDateFormatter diff --git a/examples/sample-app/composeApp/src/iosMain/kotlin/dev/uatu/sample/Platform.ios.kt b/examples/folio/composeApp/src/iosMain/kotlin/app/folio/platform/Platform.ios.kt similarity index 91% rename from examples/sample-app/composeApp/src/iosMain/kotlin/dev/uatu/sample/Platform.ios.kt rename to examples/folio/composeApp/src/iosMain/kotlin/app/folio/platform/Platform.ios.kt index 166f210..6276e14 100644 --- a/examples/sample-app/composeApp/src/iosMain/kotlin/dev/uatu/sample/Platform.ios.kt +++ b/examples/folio/composeApp/src/iosMain/kotlin/app/folio/platform/Platform.ios.kt @@ -1,4 +1,4 @@ -package dev.uatu.sample +package app.folio.platform import platform.Foundation.NSDate import platform.Foundation.NSUUID diff --git a/examples/sample-app/composeApp/src/iosMain/kotlin/dev/uatu/sample/ui/BackHandler.ios.kt b/examples/folio/composeApp/src/iosMain/kotlin/app/folio/ui/BackHandler.ios.kt similarity index 94% rename from examples/sample-app/composeApp/src/iosMain/kotlin/dev/uatu/sample/ui/BackHandler.ios.kt rename to examples/folio/composeApp/src/iosMain/kotlin/app/folio/ui/BackHandler.ios.kt index 4c02ba8..bb3dcab 100644 --- a/examples/sample-app/composeApp/src/iosMain/kotlin/dev/uatu/sample/ui/BackHandler.ios.kt +++ b/examples/folio/composeApp/src/iosMain/kotlin/app/folio/ui/BackHandler.ios.kt @@ -1,4 +1,4 @@ -package dev.uatu.sample.ui +package app.folio.ui import androidx.compose.runtime.Composable import androidx.compose.runtime.DisposableEffect diff --git a/examples/sample-app/composeApp/src/iosMain/kotlin/dev/uatu/sample/ui/IosBackGesture.kt b/examples/folio/composeApp/src/iosMain/kotlin/app/folio/ui/IosBackGesture.kt similarity index 94% rename from examples/sample-app/composeApp/src/iosMain/kotlin/dev/uatu/sample/ui/IosBackGesture.kt rename to examples/folio/composeApp/src/iosMain/kotlin/app/folio/ui/IosBackGesture.kt index a04bd46..de1bcc5 100644 --- a/examples/sample-app/composeApp/src/iosMain/kotlin/dev/uatu/sample/ui/IosBackGesture.kt +++ b/examples/folio/composeApp/src/iosMain/kotlin/app/folio/ui/IosBackGesture.kt @@ -1,4 +1,4 @@ -package dev.uatu.sample.ui +package app.folio.ui object IosBackGesture { private var nextId = 0 diff --git a/examples/sample-app/composeApp/src/sqlMain/kotlin/dev/uatu/sample/DriverFactory.kt b/examples/folio/composeApp/src/sqlMain/kotlin/app/folio/data/DriverFactory.kt similarity index 80% rename from examples/sample-app/composeApp/src/sqlMain/kotlin/dev/uatu/sample/DriverFactory.kt rename to examples/folio/composeApp/src/sqlMain/kotlin/app/folio/data/DriverFactory.kt index 5c4854c..50b6df4 100644 --- a/examples/sample-app/composeApp/src/sqlMain/kotlin/dev/uatu/sample/DriverFactory.kt +++ b/examples/folio/composeApp/src/sqlMain/kotlin/app/folio/data/DriverFactory.kt @@ -1,4 +1,4 @@ -package dev.uatu.sample +package app.folio.data import app.cash.sqldelight.db.SqlDriver diff --git a/examples/sample-app/composeApp/src/sqlMain/kotlin/dev/uatu/sample/SqlLedgerStore.kt b/examples/folio/composeApp/src/sqlMain/kotlin/app/folio/data/SqlLedgerStore.kt similarity index 94% rename from examples/sample-app/composeApp/src/sqlMain/kotlin/dev/uatu/sample/SqlLedgerStore.kt rename to examples/folio/composeApp/src/sqlMain/kotlin/app/folio/data/SqlLedgerStore.kt index 6110535..7357c76 100644 --- a/examples/sample-app/composeApp/src/sqlMain/kotlin/dev/uatu/sample/SqlLedgerStore.kt +++ b/examples/folio/composeApp/src/sqlMain/kotlin/app/folio/data/SqlLedgerStore.kt @@ -1,9 +1,9 @@ -package dev.uatu.sample +package app.folio.data import app.cash.sqldelight.coroutines.asFlow import app.cash.sqldelight.coroutines.mapToList import app.cash.sqldelight.coroutines.mapToOneOrNull -import dev.uatu.sample.db.LedgerDatabase +import app.folio.db.LedgerDatabase import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.SupervisorJob @@ -58,7 +58,7 @@ class SqlLedgerStore(factory: DriverFactory) : LedgerStore { } } -private fun dev.uatu.sample.db.Txns.toDomain(): Transaction = +private fun app.folio.db.Txns.toDomain(): Transaction = Transaction( id = id, accountId = accountId, diff --git a/examples/sample-app/composeApp/src/wasmJsMain/kotlin/dev/uatu/sample/Snapshot.kt b/examples/folio/composeApp/src/wasmJsMain/kotlin/app/folio/data/Snapshot.kt similarity index 99% rename from examples/sample-app/composeApp/src/wasmJsMain/kotlin/dev/uatu/sample/Snapshot.kt rename to examples/folio/composeApp/src/wasmJsMain/kotlin/app/folio/data/Snapshot.kt index 2c1a33b..3bbd0da 100644 --- a/examples/sample-app/composeApp/src/wasmJsMain/kotlin/dev/uatu/sample/Snapshot.kt +++ b/examples/folio/composeApp/src/wasmJsMain/kotlin/app/folio/data/Snapshot.kt @@ -1,4 +1,4 @@ -package dev.uatu.sample +package app.folio.data internal data class Snapshot( val accounts: List<Account>, diff --git a/examples/sample-app/composeApp/src/wasmJsMain/kotlin/dev/uatu/sample/WebLedgerStore.kt b/examples/folio/composeApp/src/wasmJsMain/kotlin/app/folio/data/WebLedgerStore.kt similarity index 98% rename from examples/sample-app/composeApp/src/wasmJsMain/kotlin/dev/uatu/sample/WebLedgerStore.kt rename to examples/folio/composeApp/src/wasmJsMain/kotlin/app/folio/data/WebLedgerStore.kt index b42c746..f31a5fc 100644 --- a/examples/sample-app/composeApp/src/wasmJsMain/kotlin/dev/uatu/sample/WebLedgerStore.kt +++ b/examples/folio/composeApp/src/wasmJsMain/kotlin/app/folio/data/WebLedgerStore.kt @@ -1,4 +1,4 @@ -package dev.uatu.sample +package app.folio.data import kotlinx.coroutines.flow.MutableStateFlow import kotlinx.coroutines.flow.StateFlow diff --git a/examples/sample-app/composeApp/src/wasmJsMain/kotlin/dev/uatu/sample/main.kt b/examples/folio/composeApp/src/wasmJsMain/kotlin/app/folio/main.kt similarity index 87% rename from examples/sample-app/composeApp/src/wasmJsMain/kotlin/dev/uatu/sample/main.kt rename to examples/folio/composeApp/src/wasmJsMain/kotlin/app/folio/main.kt index cbd65fd..baaeb83 100644 --- a/examples/sample-app/composeApp/src/wasmJsMain/kotlin/dev/uatu/sample/main.kt +++ b/examples/folio/composeApp/src/wasmJsMain/kotlin/app/folio/main.kt @@ -1,8 +1,9 @@ -package dev.uatu.sample +package app.folio import androidx.compose.ui.ExperimentalComposeUiApi import androidx.compose.ui.window.ComposeViewport -import dev.uatu.sample.ui.WebBackGesture +import app.folio.data.Repository +import app.folio.ui.WebBackGesture import kotlinx.browser.document import kotlinx.browser.window import org.w3c.dom.events.Event diff --git a/examples/sample-app/composeApp/src/wasmJsMain/kotlin/dev/uatu/sample/Format.wasmJs.kt b/examples/folio/composeApp/src/wasmJsMain/kotlin/app/folio/platform/Format.wasmJs.kt similarity index 97% rename from examples/sample-app/composeApp/src/wasmJsMain/kotlin/dev/uatu/sample/Format.wasmJs.kt rename to examples/folio/composeApp/src/wasmJsMain/kotlin/app/folio/platform/Format.wasmJs.kt index 84c2e1f..bc99afc 100644 --- a/examples/sample-app/composeApp/src/wasmJsMain/kotlin/dev/uatu/sample/Format.wasmJs.kt +++ b/examples/folio/composeApp/src/wasmJsMain/kotlin/app/folio/platform/Format.wasmJs.kt @@ -1,4 +1,4 @@ -package dev.uatu.sample +package app.folio.platform private val MONTHS = arrayOf( "Jan", "Feb", "Mar", "Apr", "May", "Jun", diff --git a/examples/sample-app/composeApp/src/wasmJsMain/kotlin/dev/uatu/sample/Platform.wasmJs.kt b/examples/folio/composeApp/src/wasmJsMain/kotlin/app/folio/platform/Platform.wasmJs.kt similarity index 96% rename from examples/sample-app/composeApp/src/wasmJsMain/kotlin/dev/uatu/sample/Platform.wasmJs.kt rename to examples/folio/composeApp/src/wasmJsMain/kotlin/app/folio/platform/Platform.wasmJs.kt index a569ac2..467b5d8 100644 --- a/examples/sample-app/composeApp/src/wasmJsMain/kotlin/dev/uatu/sample/Platform.wasmJs.kt +++ b/examples/folio/composeApp/src/wasmJsMain/kotlin/app/folio/platform/Platform.wasmJs.kt @@ -1,4 +1,4 @@ -package dev.uatu.sample +package app.folio.platform import kotlin.random.Random diff --git a/examples/sample-app/composeApp/src/wasmJsMain/kotlin/dev/uatu/sample/ui/BackHandler.wasmJs.kt b/examples/folio/composeApp/src/wasmJsMain/kotlin/app/folio/ui/BackHandler.wasmJs.kt similarity index 97% rename from examples/sample-app/composeApp/src/wasmJsMain/kotlin/dev/uatu/sample/ui/BackHandler.wasmJs.kt rename to examples/folio/composeApp/src/wasmJsMain/kotlin/app/folio/ui/BackHandler.wasmJs.kt index 243fa31..747c6b0 100644 --- a/examples/sample-app/composeApp/src/wasmJsMain/kotlin/dev/uatu/sample/ui/BackHandler.wasmJs.kt +++ b/examples/folio/composeApp/src/wasmJsMain/kotlin/app/folio/ui/BackHandler.wasmJs.kt @@ -1,4 +1,4 @@ -package dev.uatu.sample.ui +package app.folio.ui import androidx.compose.runtime.Composable import androidx.compose.runtime.DisposableEffect diff --git a/examples/sample-app/composeApp/src/wasmJsMain/resources/index.html b/examples/folio/composeApp/src/wasmJsMain/resources/index.html similarity index 94% rename from examples/sample-app/composeApp/src/wasmJsMain/resources/index.html rename to examples/folio/composeApp/src/wasmJsMain/resources/index.html index 7a40391..dfc1982 100644 --- a/examples/sample-app/composeApp/src/wasmJsMain/resources/index.html +++ b/examples/folio/composeApp/src/wasmJsMain/resources/index.html @@ -3,7 +3,7 @@ <head> <meta charset="utf-8" /> <meta name="viewport" content="width=device-width, initial-scale=1.0, maximum-scale=1.0, user-scalable=no" /> - <title>Uatu Sample + Folio