Skip to content

Commit 036d2f3

Browse files
authored
Code refactoring (#41)
- Move code from java to kotlin - Add Jetpack Compose support - Add state machine and simplify library interface
1 parent d5d69ed commit 036d2f3

128 files changed

Lines changed: 6381 additions & 917 deletions

File tree

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

.github/workflows/ci.yml

Lines changed: 107 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,107 @@
1+
name: CI
2+
3+
on:
4+
push:
5+
branches: [ main, master ]
6+
pull_request:
7+
branches: [ '**' ]
8+
9+
concurrency:
10+
group: ci-${{ github.workflow }}-${{ github.ref }}
11+
cancel-in-progress: true
12+
13+
jobs:
14+
build-test-cover:
15+
name: Lint, Test, Coverage
16+
runs-on: ubuntu-latest
17+
timeout-minutes: 30
18+
19+
steps:
20+
- name: Checkout
21+
uses: actions/checkout@v4
22+
23+
- name: Setup Java 21
24+
uses: actions/setup-java@v4
25+
with:
26+
distribution: temurin
27+
java-version: '21'
28+
29+
- name: Setup Android SDK
30+
uses: android-actions/setup-android@v3
31+
32+
- name: Accept SDK licenses and install packages
33+
shell: bash
34+
run: |
35+
sdkmanager --licenses <<<'y\ny\ny\ny\ny' > /dev/null
36+
sdkmanager \
37+
"platform-tools" \
38+
"platforms;android-36" \
39+
"build-tools;35.0.0"
40+
41+
- name: Setup Gradle
42+
uses: gradle/actions/setup-gradle@v4
43+
44+
- name: Verify Gradle
45+
run: ./gradlew --version
46+
47+
- name: Lint
48+
run: ./gradlew --stacktrace lint
49+
50+
- name: Unit tests (all modules)
51+
run: ./gradlew --stacktrace test
52+
53+
- name: Coverage (Kover) for library module
54+
run: |
55+
./gradlew --stacktrace :masked-edittext:koverXmlReport :masked-edittext:koverHtmlReport
56+
57+
- name: Publish test reports
58+
if: always()
59+
uses: actions/upload-artifact@v4
60+
with:
61+
name: test-reports
62+
path: |
63+
**/build/reports/tests/**
64+
**/build/test-results/**
65+
66+
- name: Publish coverage reports (Kover)
67+
if: always()
68+
uses: actions/upload-artifact@v4
69+
with:
70+
name: coverage-kover
71+
path: |
72+
masked-edittext/build/reports/kover/**
73+
74+
- name: Coverage summary
75+
if: always()
76+
shell: bash
77+
run: |
78+
set -euo pipefail
79+
REPORT_XML="masked-edittext/build/reports/kover/xml/report.xml"
80+
if [[ -f "$REPORT_XML" ]]; then
81+
python3 - << 'PY'
82+
import xml.etree.ElementTree as ET
83+
import os
84+
path = os.environ.get('REPORT_XML', 'masked-edittext/build/reports/kover/xml/report.xml')
85+
tree = ET.parse(path)
86+
root = tree.getroot()
87+
line = next((c for c in root.findall('counter') if c.get('type')=='LINE'), None)
88+
inst = next((c for c in root.findall('counter') if c.get('type')=='INSTRUCTION'), None)
89+
def pct(counter):
90+
if counter is None: return None
91+
missed = int(counter.get('missed', '0'))
92+
covered = int(counter.get('covered', '0'))
93+
total = missed + covered
94+
return (covered * 100.0 / total) if total else 0.0
95+
lines = pct(line)
96+
instructions = pct(inst)
97+
summary = []
98+
if lines is not None:
99+
summary.append(f"Line coverage: {lines:.2f}%")
100+
if instructions is not None:
101+
summary.append(f"Instruction coverage: {instructions:.2f}%")
102+
print("\n".join(summary) or "No coverage counters found.")
103+
PY
104+
else
105+
echo "Coverage report not found at $REPORT_XML"
106+
fi >> "$GITHUB_STEP_SUMMARY"
107+

.github/workflows/release.yml

Lines changed: 97 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,97 @@
1+
name: Release to Maven Central
2+
3+
on:
4+
push:
5+
tags:
6+
- 'v*'
7+
workflow_dispatch:
8+
9+
jobs:
10+
publish:
11+
name: Publish artifacts
12+
runs-on: ubuntu-latest
13+
timeout-minutes: 60
14+
15+
env:
16+
# Common envs many Gradle configs consume for Sonatype + Signing
17+
OSSRH_USERNAME: ${{ secrets.OSSRH_USERNAME }}
18+
OSSRH_PASSWORD: ${{ secrets.OSSRH_PASSWORD }}
19+
SIGNING_KEY_ID: ${{ secrets.SIGNING_KEY_ID }}
20+
SIGNING_KEY: ${{ secrets.SIGNING_KEY }}
21+
SIGNING_PASSWORD: ${{ secrets.SIGNING_PASSWORD }}
22+
# Also expose via ORG_GRADLE_PROJECT_* so Gradle can read without extra wiring
23+
ORG_GRADLE_PROJECT_sonatypeUsername: ${{ secrets.OSSRH_USERNAME }}
24+
ORG_GRADLE_PROJECT_sonatypePassword: ${{ secrets.OSSRH_PASSWORD }}
25+
ORG_GRADLE_PROJECT_mavenCentralUsername: ${{ secrets.OSSRH_USERNAME }}
26+
ORG_GRADLE_PROJECT_mavenCentralPassword: ${{ secrets.OSSRH_PASSWORD }}
27+
ORG_GRADLE_PROJECT_signingKeyId: ${{ secrets.SIGNING_KEY_ID }}
28+
ORG_GRADLE_PROJECT_signingKey: ${{ secrets.SIGNING_KEY }}
29+
ORG_GRADLE_PROJECT_signingPassword: ${{ secrets.SIGNING_PASSWORD }}
30+
31+
steps:
32+
- name: Checkout
33+
uses: actions/checkout@v4
34+
35+
- name: Setup Java 21
36+
uses: actions/setup-java@v4
37+
with:
38+
distribution: temurin
39+
java-version: '21'
40+
41+
- name: Setup Android SDK
42+
uses: android-actions/setup-android@v3
43+
44+
- name: Accept SDK licenses and install packages
45+
shell: bash
46+
run: |
47+
sdkmanager --licenses <<<'y\ny\ny\ny\ny' > /dev/null
48+
sdkmanager \
49+
"platform-tools" \
50+
"platforms;android-36" \
51+
"build-tools;35.0.0"
52+
53+
- name: Setup Gradle
54+
uses: gradle/actions/setup-gradle@v4
55+
56+
- name: Import GPG key (for signing)
57+
if: env.SIGNING_KEY != ''
58+
shell: bash
59+
run: |
60+
set -euo pipefail
61+
echo "Preparing GPG for artifact signing"
62+
# Try base64 decode first; if it fails, import raw armored key
63+
tmpkey=$(mktemp)
64+
if echo "$SIGNING_KEY" | base64 --decode --ignore-garbage > "$tmpkey" 2>/dev/null && grep -q "BEGIN PGP PRIVATE KEY BLOCK" "$tmpkey"; then
65+
echo "Detected base64-encoded key; importing decoded key"
66+
gpg --batch --import "$tmpkey"
67+
else
68+
echo "Importing key as provided (armored)"
69+
printf "%s" "$SIGNING_KEY" | gpg --batch --import
70+
fi
71+
rm -f "$tmpkey"
72+
73+
- name: Build release AAR (sanity)
74+
run: ./gradlew --stacktrace :masked-edittext:assembleRelease
75+
76+
- name: Publish to Sonatype or generic Maven
77+
shell: bash
78+
run: |
79+
set -euo pipefail
80+
echo "Checking for Nexus Publish tasks..."
81+
if ./gradlew -q tasks --all | grep -q "publishToSonatype"; then
82+
echo "Using Gradle Nexus Publish Plugin"
83+
./gradlew --stacktrace publishToSonatype closeAndReleaseSonatypeStagingRepository
84+
else
85+
echo "Falling back to generic 'publish'"
86+
./gradlew --stacktrace publish
87+
fi
88+
89+
- name: Upload built artifacts
90+
if: always()
91+
uses: actions/upload-artifact@v4
92+
with:
93+
name: release-artifacts
94+
path: |
95+
masked-edittext/build/outputs/aar/*.aar
96+
**/build/publications/**
97+

.gitignore

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -62,3 +62,6 @@ freeline.py
6262
freeline/
6363
freeline_project_description.json
6464

65+
/.zed/
66+
/.tool-versions
67+
.DS_Store

AGENTS.md

Lines changed: 41 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,41 @@
1+
# Repository Guidelines
2+
3+
## Project Structure & Module Organization
4+
- `masked-edittext/` contains the library module.
5+
- Core masking and state: `src/main/java/com/github/pinball83/maskededittext` (e.g., `MaskFormatter.kt`, `InputStateMachine.kt`, `CursorPolicyController.kt`).
6+
- Jetpack Compose API: `src/main/java/com/github/pinball83/maskededittext/compose`.
7+
- Android resources: `src/main/res`.
8+
- `demo_app/` and `demo_app_compose/` are sample apps. Keep shared helpers in the library, not in demos.
9+
- Tests live beside code: JVM specs under `src/test`, Compose instrumentation under `src/androidTest`. Gradle scripts stay at module roots.
10+
11+
## Build, Test & Development Commands
12+
- `./gradlew assembleRelease` builds the library AAR and validates release config.
13+
- `./gradlew :masked-edittext:publishToMavenLocal` exercises the publishing pipeline and produces a local snapshot for integration testing.
14+
- `./gradlew test` runs JVM + Robolectric suites across modules; execute before every push.
15+
- `./gradlew connectedAndroidTest` launches instrumentation and Compose UI tests on a device or emulator.
16+
- `./gradlew lint` runs Android Lint and Compose metrics; address findings or document accepted risk.
17+
18+
## Coding Style & Naming Conventions
19+
- Kotlin is the default language; use 4-space indents, trailing commas where they improve diffs, and prefer expression-bodied functions when clear.
20+
- Classes, composables, and test fixtures use `PascalCase`; methods and variables use `camelCase`; constants stay in `CONSTANT_CASE`.
21+
- Compose API lives under the `compose/` package; shared mask logic stays in the root package. Keep any Java interop isolated and documented.
22+
- Run `./gradlew lint` before committing; avoid suppressing warnings unless a tracking issue exists.
23+
24+
## Testing Guidelines
25+
- Use JUnit4 + Robolectric for mask behaviour; mirror production packages in `src/test` for clarity.
26+
- Compose UI assertions belong in `src/androidTest` with `createAndroidComposeRule` utilities.
27+
- Name tests as `functionUnderTest_state_expectedResult` to emphasise behaviour.
28+
- Prioritise coverage around mask configuration, cursor placement, locale handling, and accessibility announcements.
29+
30+
## Refactoring Summary & Docs
31+
- Kotlin-first rewrite with Java interop preserved.
32+
- Unified mask core shared by View and Compose.
33+
- New Compose API with grouped options (`MaskedOptions`, `MaskedVisualOptions`, `MaskedInputOptions`).
34+
- Deprecated/no-op XML attributes kept for compatibility: `replacementChar`, `deleteChar`, `maskIconColor`.
35+
- See `MODERNIZATION.md`, `MODERNIZATION_SUMMARY.md`, and `API_REFACTORING_GUIDE.md` for details and migration guidance.
36+
37+
## Commit & Pull Request Guidelines
38+
- Match concise, imperative history (`Update Kotlin version`, `Remove unused java code`); scope each commit to one concern.
39+
- PR descriptions should outline intent, list touched modules, and reference issues or Jira tickets when applicable.
40+
- Attach emulator logs or screenshots whenever UI or UX changes are visible.
41+
- Confirm `./gradlew lint test` passes locally and note the run in the PR summary before requesting review.

0 commit comments

Comments
 (0)