Summary

Multi-step registration wizard that collects account type, personal info, interests/genre tags, username, birthday, and credentials. Deep-reworked at commit 841ec58 (branch edit/mobile-audit-ux-polish). For screen visual design spec see auth-screens.


Files

FileRole
lib/features/auth/screens/registration_wizard_screen.dartUI, step rendering, animations
lib/features/auth/notifiers/registration_wizard_notifier.dartState, validation, Supabase submit
lib/shared/widgets/flow_date_wheel_picker.dartBirthday date picker widget

State Model — RegistrationWizardState

Key fields (all others in the file itself):

final WizardStep currentStep;          // enum-driven step machine
final AccountType? selectedType;       // personal | creator | business
final String? fieldError;              // inline per-step error message
final bool isValidating;               // async check in progress → CTA spinner
final bool isSubmitting;               // final submit in progress
final String? lastSelectedInterest;    // drives tag affinity ordering
final UsernameCheckStatus usernameCheckStatus; // idle|checking|available|taken

Field is selectedTypenot selectedAccountType. Any screen code referencing the old name will throw a getter-not-found error.


Step Machine — WizardStep Enum

welcome → accountType → personal (name+birthday+username) →
  [creator: stageName+genre] | [business: name+category+location] →
  vibeCheck → credentials → submit

Sub-steps within personal/creator/business are tracked by pageIndex inside each step group.


Notifier — Key Methods

validateThenCall(l10n, onValid)

Every “Continue” button calls this. Flow:

  1. Sets isValidating = true, clears fieldError
  2. Runs _validateCurrentStep(l10n) — async (may hit Supabase)
  3. Error → sets fieldError, clears isValidating (CTA re-enables)
  4. OK → clears isValidating, calls onValid()

setUsername(value) — Live availability

Debounces 450 ms, then calls _runUsernameAvailabilityCheck() which queries profiles.username with .maybeSingle(). Sets usernameCheckStatusavailable or taken. Network failures reset to idle silently — the hard check on submit is the authoritative guard.

orderedTags(allTags, selectedNames, lastSelectedName, {affinityWeights})

Static method. Order:

  1. Selected (in selection order)
  2. Neural path: unselected sorted by P(B|lastSelected) desc (if affinityWeights non-empty)
  3. Structural fallback: same parentId or same category, sorted by usageCount
  4. Rest by usageCount desc

Validation Matrix

StepRule
Personal nameFirst name not empty
BirthdayAge ≥ 18
UsernameFormat [a-zA-Z0-9_.] 3–30 chars + Supabase uniqueness
Creator stage nameNot empty
Business name + categoryBoth not empty
Business locationAddress not empty
CredentialsEmail format + password ≥ 8 chars
SubmitRe-validate credentials; email already registeredfieldError (not SnackBar)

Tag System

Tables: tags (id, name, category ENUM, parent_id, usage_count), tag_affinity (tag_a_id, tag_b_id, weight FLOAT, co_occurrences, computed_at)

Weight formula: P(B|A) = co_occurrences(A,B) / count(profiles_with_A) — asymmetric.

Providers:

  • tagListProvider — all tags, global singleton, prefetched at wizard mount via ref.watch in _WizardBody.build
  • tagAffinityProvider(tagId)FutureProvider.autoDispose.family, per-tag weights

Chip styling (_tagChip):

  • Selected: electricCoral fill, white label, FontWeight.w600, showCheckmark: false
  • Unselected: white fill, textPrimary, FontWeight.w400, dividerLight border
  • Shape: RoundedRectangleBorder(borderRadius: 20) — pill

Date Picker — FlowDateWheelPicker

Two modes toggled by top-right button:

  • Wheel (default): Three ListWheelScrollView columns (DD / MMM / YYYY). Arrow Up/Down scrolls; Arrow Right/Tab advances focus; focused column has coral border.
  • Keyboard: Three TextField fields, day and month auto-advance after 2 digits.

Tab-escape fix: Tab on year column returns KeyEventResult.ignored (not handled) so Flutter’s tab order advances naturally to the “Continue” button.


Provider Setup

final registrationWizardNotifierProvider =
    StateNotifierProvider.autoDispose<RegistrationWizardNotifier, RegistrationWizardState>(
  (ref) => RegistrationWizardNotifier(),
);

autoDispose ensures state resets on every navigation away — the welcome step is always shown fresh on re-entry.


Firebase Guard

NotificationService._firebaseAvailable is set only after successful Firebase.initializeApp(). All FCM calls are gated behind this flag to prevent [core/no-app] exceptions when google-services.json is absent.


l10n Keys

All in app_en.arb, app_it.arb, app_es.arb:

wizardValidationFirstNameRequired   wizardValidationAgeRestriction
wizardValidationUsernameRequired    wizardValidationUsernameFormat
wizardValidationUsernameTaken       wizardValidationEmailRequired
wizardValidationEmailFormat         wizardValidationPasswordTooShort
wizardValidationBusinessNameRequired wizardValidationBusinessCategoryRequired
wizardValidationStageNameRequired   wizardValidationLocationRequired
wizardValidationEmailTaken
dateInputDay   dateInputMonth   dateInputYear
dateInputModeKeyboard   dateInputModeWheel

For welcome/auth screen keys see auth-screens.


auth-screens — visual design spec for all auth screens localization — ARB workflow context-map — tag & recommendation system inline spec