Skip to content

Configurable + custom registration/profile fields (#255) - #26

Merged
fabiodalez-dev merged 5 commits into
mainfrom
feat/255-registration-fields
Aug 5, 2026
Merged

Configurable + custom registration/profile fields (#255)#26
fabiodalez-dev merged 5 commits into
mainfrom
feat/255-registration-fields

Conversation

@fabiodalez-dev

@fabiodalez-dev fabiodalez-dev commented Jul 21, 2026

Copy link
Copy Markdown
Owner

Consumes the configurable registration/profile field schema exposed by Pinakes and renders the matching Android form controls.

Models and networking

  • Adds field-schema models and custom field value serialization.
  • Extends registration and profile requests with configured built-in fields and custom fields.
  • Keeps profile updates compatible with existing users when a custom field becomes required after registration.

Screens

  • Registration renders the configured built-in/custom fields and validates required fields before submission.
  • Profile renders editable configured fields, uses a localized gender selector, and emits only the server-supported values: M, F, Altro, or empty.
  • Boolean, date, select, multiselect, number, email, phone, URL, and text custom fields are supported.

Design and localization

  • Reuses the existing Pinakes UI components and Material 3 patterns.
  • Adds matching strings in values, values-en, values-fr, and values-es; all four catalogs contain the same 429 keys.

Review fixes

  • Rebased onto current main.
  • Bumps the app to versionCode 10 / versionName 1.4.0.
  • Aligns profile required-field behavior with the server PATCH contract.
  • Prevents unsupported free-text gender values from being silently normalized by the server.

Verification

  • ./gradlew clean testDebugUnitTest assembleDebug lintDebug
  • 93 unit tests, 0 failures/errors/skips.
  • Android lint and debug assembly pass.

Known follow-up (non-blocking)

  • Server-side 422 responses are still surfaced through the existing generic error path; field-level error mapping can be added separately.

Summary by CodeRabbit

  • Nuove funzionalità
    • La registrazione supporta campi personalizzati definiti dall’istanza, con caricamento dinamico dello schema, validazione dei campi obbligatori e possibilità di riprovare in caso di errore.
    • Il profilo consente di modificare ulteriori dati personali e campi personalizzati, visualizzare i dati della tessera e aggiornare o cancellare singoli valori.
  • Localizzazione
    • Aggiornate le traduzioni per i nuovi campi, errori e azioni.
  • Test
    • Aggiunti test per la validazione dei moduli di registrazione e profilo.

@coderabbitai

coderabbitai Bot commented Jul 21, 2026

Copy link
Copy Markdown

Review Change Stack

Walkthrough

Il PR aggiunge la discovery dei campi di registrazione, il supporto ai campi personalizzati nella registrazione e nel profilo, nuovi dati personali e le informazioni della tessera.

Changes

Campi dinamici e profilo

Layer / File(s) Summary
Contratti e accesso API
app/src/main/java/com/pinakes/app/data/model/Models.kt, app/src/main/java/com/pinakes/app/data/network/PinakesApi.kt, app/src/main/java/com/pinakes/app/data/repository/*.kt, app/build.gradle.kts
I modelli supportano schema, valori personalizzati e PATCH parziali. L’API espone auth/registration-fields. I repository collegano discovery, registrazione e aggiornamento del profilo. La versione passa a 1.4.0.
Registrazione con campi dinamici
app/src/main/java/com/pinakes/app/ui/screens/login/RegisterScreen.kt, app/src/test/java/com/pinakes/app/RegisterUiStateTest.kt, i18n/*.json
Il ViewModel carica e valida lo schema. Normalizza i valori personalizzati e invia la registrazione. La UI mostra input tipizzati, loading, retry e messaggi localizzati.
Editing esteso del profilo
app/src/main/java/com/pinakes/app/ui/screens/profile/ProfileViewModel.kt, app/src/main/java/com/pinakes/app/ui/screens/profile/ProfileScreen.kt, app/src/test/java/com/pinakes/app/ProfileUiStateTest.kt, i18n/*.json
L’editor gestisce dati personali, date, genere e campi personalizzati. Valida i campi richiesti e invia una PATCH differenziale. Il profilo mostra numero e scadenza della tessera.

Estimated code review effort: 4 (Complex) | ~45 minutes

Sequence Diagram(s)

sequenceDiagram
  participant RegisterScreen
  participant RegisterViewModel
  participant AuthRepository
  participant PinakesApi
  RegisterScreen->>RegisterViewModel: submit()
  RegisterViewModel->>AuthRepository: register(customFields)
  AuthRepository->>PinakesApi: POST registration request
  PinakesApi-->>AuthRepository: registration result
  AuthRepository-->>RegisterViewModel: ApiResult
  RegisterViewModel-->>RegisterScreen: registration state
Loading

Poem

Sono un coniglio dal passo leggero,
compilo campi con schema sincero.
La form si aggiorna, il profilo anche,
ogni valore segue regole bianche.
Tra PATCH e tessere, salto felice.

🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 29.85% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (4 passed)
Check name Status Explanation
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed Il titolo descrive il principale scopo del PR: aggiungere campi di registrazione e profilo configurabili e personalizzati. Corrisponde direttamente ai cambiamenti nel codice.
✨ Finishing Touches 💡 1
📝 Generate docstrings 💡
  • Create stacked PR
  • Commit on current branch
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch feat/255-registration-fields

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands.

@fabiodalez-dev

Copy link
Copy Markdown
Owner Author

🔎 Adams Review — PR #26 (configurable + custom registration/profile fields)

Adversarial pass (correctness / structural contract-adherence / UX). Correctness-clean — no serialization, decode, or contract-drift bugs. Three UX-hardening items surfaced, all non-blocking; they overlap the follow-ups already noted in the PR description.

⚠️ Still needs a Gradle build to confirm compilation — reviewed statically (no Android SDK in the review environment).

✅ Correctness (L1) — clean

No decode-breaking shapes, no @SerialName drift, no null-deref / wrong-branch / unresolved-ref found across Models, RegisterScreen, ProfileScreen, ViewModels. New @Serializable models are compile-plausible with nullable/defaulted fields.

✅ Structural / contract adherence (L2) — clean

Verified exact against the server contract (PR #277):

  • Models match server shapes incl. the map-vs-list distinction (CustomFieldDef for register, CustomFieldValue for profile).
  • builtinRequired(key) = builtinFields[key]?.required ?: true — correctly defaults to required when a key is absent (matching the server default); nome/email/password hardcoded-required, only cognome/telefono/indirizzo schema-driven.
  • options/select modeled as emptyList() default, never consumed → forward-compat tolerated, not assumed.
  • customFields?.takeIf { it.isNotEmpty() } → empty map omitted (instances with no custom fields still register).
  • explicitNulls = false → PATCH /me stays partial (unsent fields not clobbered).

ℹ️ UX hardening (L5) — 3 confirmed, non-blocking

  1. No loading gate while the registration schema loads. loadSchema() runs in init async; the submit button is gated only against in-flight submits (RegisterScreen.kt:117), not against the schema fetch. A fast user can submit before customFields populate → the missingCustom required check passes vacuously → payload omits custom_fields → server 422 on a required custom field that was never shown. Fix: a schemaLoading flag that disables submit (or shows a spinner) until the fetch resolves.
  2. Silent schema-fetch failure. On GET /auth/registration-fields failure the code keeps defaults with no error/retry (ProfileViewModel.kt:66, same in RegisterViewModel — deliberate "never block registration"). Trade-off: required custom fields silently never appear → same 422 trap. A subtle inline notice ("couldn't load extra fields — retry") would close it without blocking signup.
  3. Profile-edit dialog has no client-side required validation. Asterisks are shown but Save is always enabled and fires (ProfileScreen.kt:505-571); clearing a required field relies on a server 422 round-trip rather than an immediate message — unlike the register flow which validates locally. (Already listed as a known follow-up in the PR.)

Verdict: merge-ready pending the Gradle build. The three UX items are polish; #1 (schema loading gate) is the one most worth doing before this ships to users. Want me to add the loading gate + the fetch-failure notice?

Lenses: L1 correctness, L2 structural, L5 UX. Findings verified against the actual Kotlin, not just flagged.

fabiodalez-dev added a commit that referenced this pull request Jul 21, 2026
…equired validation

Adams-review UX follow-ups on the configurable/custom fields work:

1. Registration schema loading gate. loadSchema() ran async in init with no
   flag gating submit, so a fast user could submit before the custom-field
   section appeared → the required-custom-field check passed vacuously →
   opaque server 422 on a field never shown. Added schemaLoading (starts true);
   submit() returns early while it's set, and the submit button shows a spinner.

2. Silent schema-fetch failure. On failure the code kept built-in defaults with
   no signal. Added schemaFailed → an inline notice + Retry (loadSchema) so the
   user knows extra fields may be missing. A failed fetch clears schemaLoading,
   so a schema-down instance still lets the user register with the defaults
   (preserves the "never block registration" intent).

3. Profile-edit required validation. The edit dialog showed asterisks but Save
   always fired, relying on a server 422. saveEdit() now validates required
   telefono/indirizzo (per the instance schema) + required custom fields
   client-side and shows an inline error (editErrorRes), cleared as the user
   fixes the field — mirroring the register flow.

New string register_schema_failed added to all four locales (en/it/fr/de);
reuses existing action_retry + register_error_required. Design-system-consistent
(PinakesTextField/PinakesTextButton/Surface/Spacing tokens). Static review only
— needs a Gradle build to confirm compilation.

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 3

🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@app/src/main/java/com/pinakes/app/ui/screens/profile/ProfileScreen.kt`:
- Around line 621-631: Update the DatePickerDialog in ProfileScreen so users can
clear an existing birth date by invoking onValueChange with an empty string,
while preserving the current save and cancel behavior for date selection and
dismissal.

In `@app/src/main/java/com/pinakes/app/ui/screens/profile/ProfileViewModel.kt`:
- Around line 38-39: Update ProfileViewModel’s builtinFields handling and the
validation paths around the profile save flow to distinguish an unloaded or
failed registration schema from a successfully loaded schema with required
fields. Track schema loading/error state; while the schema is unavailable, do
not treat empty telefono or indirizzo values as required, and preserve
required-field validation once the schema loads successfully. Apply the same
behavior to the related logic near the schema-loading and save-validation
sections.
- Around line 121-124: Estendi la validazione locale in saveEdit() per includere
nome obbligatorio e cognome secondo la configurazione esistente, impedendo
l’invio di valori vuoti al server. Aggiorna inoltre onEditNome() e
onEditCognome() per azzerare editErrorRes come già avviene negli altri handler
di modifica.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

Run ID: a4873a3d-e554-413f-82eb-cc9a597fe4b6

📥 Commits

Reviewing files that changed from the base of the PR and between 4c8522f and 88cc03d.

📒 Files selected for processing (11)
  • app/src/main/java/com/pinakes/app/data/model/Models.kt
  • app/src/main/java/com/pinakes/app/data/network/PinakesApi.kt
  • app/src/main/java/com/pinakes/app/data/repository/AuthRepository.kt
  • app/src/main/java/com/pinakes/app/data/repository/ProfileRepository.kt
  • app/src/main/java/com/pinakes/app/ui/screens/login/RegisterScreen.kt
  • app/src/main/java/com/pinakes/app/ui/screens/profile/ProfileScreen.kt
  • app/src/main/java/com/pinakes/app/ui/screens/profile/ProfileViewModel.kt
  • i18n/de.json
  • i18n/en.json
  • i18n/fr.json
  • i18n/it.json

Comment thread app/src/main/java/com/pinakes/app/ui/screens/profile/ProfileScreen.kt Outdated
Comment thread app/src/main/java/com/pinakes/app/ui/screens/profile/ProfileViewModel.kt Outdated
fabiodalez-dev added a commit that referenced this pull request Jul 21, 2026
…unit tests

CodeRabbit review of the #255 mobile work:

1. (major, a regression from the earlier review-fix) Profile edit could not be
   saved when the registration schema was unloaded/failed: builtinRequired()
   defaulted to true on an empty map, so an optional-but-empty telefono/indirizzo
   blocked saving. Profile builtinRequired() now defaults to FALSE when the key
   is absent — the user already has an account, so an unknown schema must not
   invent a required field; the server stays authoritative. Registration keeps
   the conservative default-true (unchanged).

2. (minor) saveEdit() now also validates nome (always required) + cognome (when
   the instance requires it) client-side; onEditNome/onEditCognome clear
   editErrorRes like the other edit handlers.

3. (minor) The birth-date DatePickerDialog gained a Clear action so an existing
   date can be removed (PATCH /me accepts the empty value) — previously only
   confirm/cancel existed. New string action_clear across all four locales.

Also: extracted the register submit gate + required-field check into pure
functions on RegisterUiState (canSubmit(), hasBlankRequiredField()) so
submit() calls them (DRY) and they are unit-testable. Added
RegisterUiStateTest (15 JUnit tests) covering builtinRequired defaults, the
submit gate (schemaLoading/loading/sent block; ready allows), and required
validation (core fields, config-required built-ins, required custom fields incl.
checkbox). Pure JUnit, matches the existing test style; still needs a Gradle
build to run in CI.
@fabiodalez-dev

Copy link
Copy Markdown
Owner Author

Addressed all three in 2d0af8d:

  1. (major, my regression) Profile edit could not be saved when the registration schema was unloaded/failed — builtinRequired() defaulted to true on an empty map, so an optional-but-empty telefono/indirizzo blocked saving. Profile-side builtinRequired() now defaults to false when the key is absent (the user already has an account; an unknown schema must not invent a required field — the server stays authoritative). Registration keeps the conservative default-true.
  2. (minor) saveEdit() now also validates nome (always required) + cognome (when the instance requires it); onEditNome/onEditCognome clear editErrorRes like the other handlers.
  3. (minor) The birth-date DatePickerDialog gained a Clear action so an existing date can be removed (PATCH accepts the empty value). New action_clear string in all four locales.

Also extracted the register submit gate + required check into pure functions (RegisterUiState.canSubmit() / hasBlankRequiredField()) and added RegisterUiStateTest (15 JUnit tests). Still needs a Gradle build to run.

Consume the new server discovery endpoint GET /api/v1/auth/registration-fields
so signup and profile honour an instance's configurable built-in fields and
admin-defined custom fields.

Models: RegistrationFieldsPayload / BuiltinFieldRule / CustomFieldDef /
CustomFieldValue. RegisterRequest gains custom_fields (Map<id,value>).
UserProfile extended with the fields GET /me already returns (telefono,
indirizzo, data_nascita, sesso, cod_fiscale, codice_tessera, card/last-access,
locale, custom_fields). UpdateProfileRequest extended (all nullable → partial
PATCH). PinakesApi: GET auth/registration-fields (no-auth).

Register: fetch the schema on entry; built-in required-ness (cognome/telefono/
indirizzo) driven by the schema (default required when absent, matching the
server); custom fields rendered dynamically by type and sent as custom_fields.
Profile: view + edit telefono/indirizzo/data_nascita (date picker)/cod_fiscale/
sesso and the custom fields via partial PATCH /me.

All new inputs reuse the app design system — PinakesTextField, Switch for
checkbox, Material3 DatePicker, Spacing/MaterialTheme tokens (no raw
OutlinedTextField). New strings added to all four locales (en/it/fr/de).

Not built here (no Android SDK in that environment) — needs a Gradle build to
confirm. Static review clean; kotlinx JSON is ignoreUnknownKeys/explicitNulls=
false so the richer server payloads never crash older behaviour.
…equired validation

Adams-review UX follow-ups on the configurable/custom fields work:

1. Registration schema loading gate. loadSchema() ran async in init with no
   flag gating submit, so a fast user could submit before the custom-field
   section appeared → the required-custom-field check passed vacuously →
   opaque server 422 on a field never shown. Added schemaLoading (starts true);
   submit() returns early while it's set, and the submit button shows a spinner.

2. Silent schema-fetch failure. On failure the code kept built-in defaults with
   no signal. Added schemaFailed → an inline notice + Retry (loadSchema) so the
   user knows extra fields may be missing. A failed fetch clears schemaLoading,
   so a schema-down instance still lets the user register with the defaults
   (preserves the "never block registration" intent).

3. Profile-edit required validation. The edit dialog showed asterisks but Save
   always fired, relying on a server 422. saveEdit() now validates required
   telefono/indirizzo (per the instance schema) + required custom fields
   client-side and shows an inline error (editErrorRes), cleared as the user
   fixes the field — mirroring the register flow.

New string register_schema_failed added to all four locales (en/it/fr/de);
reuses existing action_retry + register_error_required. Design-system-consistent
(PinakesTextField/PinakesTextButton/Surface/Spacing tokens). Static review only
— needs a Gradle build to confirm compilation.
…unit tests

CodeRabbit review of the #255 mobile work:

1. (major, a regression from the earlier review-fix) Profile edit could not be
   saved when the registration schema was unloaded/failed: builtinRequired()
   defaulted to true on an empty map, so an optional-but-empty telefono/indirizzo
   blocked saving. Profile builtinRequired() now defaults to FALSE when the key
   is absent — the user already has an account, so an unknown schema must not
   invent a required field; the server stays authoritative. Registration keeps
   the conservative default-true (unchanged).

2. (minor) saveEdit() now also validates nome (always required) + cognome (when
   the instance requires it) client-side; onEditNome/onEditCognome clear
   editErrorRes like the other edit handlers.

3. (minor) The birth-date DatePickerDialog gained a Clear action so an existing
   date can be removed (PATCH /me accepts the empty value) — previously only
   confirm/cancel existed. New string action_clear across all four locales.

Also: extracted the register submit gate + required-field check into pure
functions on RegisterUiState (canSubmit(), hasBlankRequiredField()) so
submit() calls them (DRY) and they are unit-testable. Added
RegisterUiStateTest (15 JUnit tests) covering builtinRequired defaults, the
submit gate (schemaLoading/loading/sent block; ready allows), and required
validation (core fields, config-required built-ins, required custom fields incl.
checkbox). Pure JUnit, matches the existing test style; still needs a Gradle
build to run in CI.
@fabiodalez-dev
fabiodalez-dev force-pushed the feat/255-registration-fields branch from 0411753 to fd0bc06 Compare August 5, 2026 13:08
@coderabbitai

coderabbitai Bot commented Aug 5, 2026

Copy link
Copy Markdown

Note

GitHub couldn't provide a complete incremental comparison for this pull request, so CodeRabbit is performing a full review instead. This review may take a little longer.

@fabiodalez-dev

Copy link
Copy Markdown
Owner Author

Review fixes completed in fd0bc06:

  • rebased onto current main; the PR is mergeable with versionCode 10 / versionName 1.4.0;
  • replaced free-text profile gender with the canonical server values (M, F, Altro, or empty);
  • aligned profile validation with the PATCH contract, so newly-required custom fields do not block unrelated edits for existing users;
  • added focused profile-state tests and completed the four-locale string set.

Validation is green: Android CI passed unit tests, lint, debug assembly, and the minified release build; CodeRabbit completed successfully. Locally, the clean verification run reported 93 tests with no failures, errors, or skips.

The CodeRabbit docstring-coverage notice was not changed: it is an advisory percentage, not a code defect or repository quality gate, and raising it to an arbitrary threshold here would add broad non-functional KDoc churn unrelated to the reviewed behavior.

@fabiodalez-dev
fabiodalez-dev merged commit fd0bc06 into main Aug 5, 2026
2 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant