Skip to content

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

Open
fabiodalez-dev wants to merge 4 commits into
mainfrom
feat/255-registration-fields
Open

Configurable + custom registration/profile fields (#255)#26
fabiodalez-dev wants to merge 4 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 new server discovery endpoint GET /api/v1/auth/registration-fields (Pinakes server PR #277) so signup and profile honour an instance's configurable built-in fields and admin-defined custom fields (#255).

Models

  • New: RegistrationFieldsPayload, BuiltinFieldRule, CustomFieldDef, CustomFieldValue.
  • RegisterRequest gains custom_fields (Map<id,value>).
  • UserProfile extended with 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 /me.
  • PinakesApi: GET auth/registration-fields (no-auth).

Screens

  • Register: fetches the schema on entry; built-in required-ness (cognome/telefono/indirizzo) driven by the schema (defaults to required when a key is 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.

Design consistency

All new inputs reuse the app's 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, 422 keys each).

Build note

Not built in the environment that produced this (no Android SDK) — needs a Gradle build to confirm. Static review is clean; kotlinx.serialization is ignoreUnknownKeys / explicitNulls=false, so the richer server payloads never crash.

Known minor follow-ups (non-blocking)

  • Register/profile surface the server's generic message on 422 rather than mapping custom_field_invalid / missing_fields to field-level errors.
  • Profile relies on the server 422 for required telefono/indirizzo (registration enforces locally).

Summary by CodeRabbit

  • Nuove funzionalità
    • La registrazione ora carica dinamicamente lo schema dei campi, valida i campi obbligatori (inclusi quelli personalizzati) e blocca l’invio finché lo schema non è pronto; in caso di errore è disponibile un riprova.
    • Il profilo consente di modificare telefono, indirizzo, data di nascita, codice fiscale, sesso e campi personalizzati; mostra i dati della tessera e aggiorna solo le informazioni cambiate, con possibilità di cancellare i valori personalizzati.
  • Localizzazione
    • Aggiornate le traduzioni per i nuovi campi del profilo e per errori/azioni legati allo schema di registrazione.
  • Test
    • Aggiunti test unitari per la logica del form di registrazione.

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.
@coderabbitai

coderabbitai Bot commented Jul 21, 2026

Copy link
Copy Markdown

Review Change Stack

No actionable comments were generated in the recent review. 🎉

ℹ️ Recent review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

Run ID: cb0ba4e4-f794-411f-8fcd-d57622c8858f

📥 Commits

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

📒 Files selected for processing (9)
  • app/build.gradle.kts
  • 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
  • app/src/test/java/com/pinakes/app/RegisterUiStateTest.kt
  • i18n/de.json
  • i18n/en.json
  • i18n/fr.json
  • i18n/it.json
🚧 Files skipped from review as they are similar to previous changes (6)
  • i18n/de.json
  • i18n/en.json
  • i18n/fr.json
  • i18n/it.json
  • app/src/main/java/com/pinakes/app/ui/screens/profile/ProfileScreen.kt
  • app/src/main/java/com/pinakes/app/ui/screens/login/RegisterScreen.kt

Walkthrough

Il PR aggiunge la discovery dei campi di registrazione, il supporto ai campi personalizzati nella registrazione e nell’aggiornamento del profilo, nuovi dati personali e la visualizzazione delle informazioni tessera.

Changes

Campi dinamici e accesso API

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 custom e PATCH parziali; l’API espone auth/registration-fields; i repository collegano discovery, registrazione e aggiornamento profilo; la versione passa a 1.4.0.

Flusso di registrazione

Layer / File(s) Summary
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 custom e invia la registrazione; la UI mostra input tipizzati, loading, retry e messaggi localizzati, con test sulla validazione dello stato.

Modifica del profilo

Layer / File(s) Summary
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, i18n/*.json
L’editor gestisce dati personali, date e campi custom con validazione e PATCH differenziale; il profilo mostra numero e scadenza tessera con le relative traduzioni.

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

Possibly related PRs

Poem

Sono un coniglio col fiocco ben teso,
tra campi custom salto sospeso.
Lo schema arriva, la form si prepara,
ogni tessera ora splende e dichiara.
PATCH dopo PATCH, con gran precisione,
carote e profili in nuova versione!

🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 24.39% 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
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed Il titolo riassume correttamente l'aggiunta di campi configurabili e custom per registrazione e profilo.
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.
✨ Finishing Touches
📝 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.

…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
…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.

…) helper

Version bump for the #255 configurable/custom registration + profile fields
(aligns with the mobile-api server plugin 1.4.0).

Also fixes the RegisterUiStateTest ready() helper: with an empty builtinFields
map the config-driven built-ins (cognome/telefono/indirizzo) default to
required, so the "ready to submit" fixture must fill them — otherwise the
all-fields-filled / optional-field assertions saw them as blank-and-required.
The production logic was correct (the Gradle build compiled cleanly); only the
test fixture was wrong. Full unit suite now green (RegisterUiStateTest + all
existing tests, 0 failures) and assembleDebug produces an installable APK.
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