Adds SignInPrompt (sign in / create account), showSignInPromptProvider, HomeScreen as ConsumerWidget, l10n de/en, widget/unit tests and a Playwright web test (#49 criterion 1, QUE-09 criterion 13, QUE-06). Refs #49 Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
304 lines
21 KiB
Markdown
304 lines
21 KiB
Markdown
# Auth-Screens: Layout-Skizzen und Texte (Spec #1, Task U1)
|
||
|
||
Grundlage: `docs/specs/1-auth-registrierung-anmeldung.md` (§ 4.4 Meldungen, § 5.4 Screens, § 6 Plattform).
|
||
Komponenten: `app/lib/core/widgets/` (siehe `docs/design-system.md`). Alle Texte unten sind
|
||
**Vorschläge** und gehen über l10n (ARB); die ARB-Dateien pflegt der flutter-dev (A1/A2).
|
||
Schlüssel sind `lowerCamelCase` mit Präfix `auth`.
|
||
|
||
## 1. Gemeinsames Layout
|
||
|
||
- Alle sechs Screens nutzen `AppFormScaffold`. Kompakt (< 600 dp): Vollbild, scrollbar, Seitenrand 16 dp.
|
||
Ab 600 dp: zentrierte Karte, max. 480 dp breit (`AppBreakpoints.authCardMaxWidth`), Innenabstand 24 dp.
|
||
- Inhalt in einer Spalte, Abstand zwischen Feldern 16 dp, zwischen Gruppen 24 dp.
|
||
- Reihenfolge = Lesereihenfolge = Tab-Reihenfolge: Titel, Banner (falls vorhanden), Felder, Checkboxen,
|
||
Primärknopf, Sekundärlinks.
|
||
- Titel: `headlineSmall`, darunter optional ein Einleitungssatz in `bodyMedium`. Titel als Semantics-Überschrift
|
||
(`Semantics(header: true)`).
|
||
- Banner (`AppBanner`) stehen **über** den Feldern, direkt unter dem Titel. Fehler am Feld stehen unter dem Feld.
|
||
- Genau ein Primärknopf (`AppPrimaryButton`, volle Breite). Sekundäraktionen sind `TextButton`s (48 dp).
|
||
- Eingaben bleiben bei jedem Fehler erhalten. Während `submitting` sind Knopf und Felder gesperrt, der Knopf
|
||
zeigt den Spinner und den gesprochenen Text `authSubmitting`.
|
||
- Nach einem Fehler setzt der Screen den Fokus auf das erste fehlerhafte Feld; bei Banner-Fehlern bleibt der
|
||
Fokus am Knopf, das Banner wird als Live-Region angesagt.
|
||
- Enter im letzten Feld sendet ab (`TextInputAction.done`), sonst `next`.
|
||
|
||
### Semantics-Struktur (Regel, Issue #41)
|
||
|
||
- Überschrift, Text und Knopf sind **getrennte Semantics-Knoten**: `heading` (`AuthScreenTitle`), Text, `button`
|
||
mit Label (`AppPrimaryButton`, `TextButton`). Das gilt in jeder Breite, auch in der Karte ab 600 dp.
|
||
- Ursache des Fehlers: `Card` ist ein Semantics-Container ohne `explicitChildNodes`. Alle Kinder ohne eigenen
|
||
Container (Titel, `Text`, Knopf) werden in dessen einen Knoten gefaltet. Seiten mit Eingabefeld fielen nicht auf,
|
||
weil `AppTextField` & Co. eigene Container sind. Der Fehler trat auf allen Plattformen ab 600 dp (Karte) auf und wurde im E2E (Web) sichtbar; auf dem Handy ohne Karte nicht.
|
||
- Deshalb kapselt `AppFormScaffold` den Karteninhalt in `Semantics(explicitChildNodes: true)`. Neue Layouts mit
|
||
`Card`/`Semantics(container: true)` um Seiteninhalt müssen das ebenfalls tun; `MergeSemantics` nur gezielt für
|
||
zusammengehörige Einzelelemente, nie um ganze Seiten.
|
||
- Banner bleiben eigene Live-Regionen (`AppBanner`), ihre Aktion ist ein eigener Knopf-Knoten.
|
||
- Test: `expectSeparateSemantics` (`app/test/features/auth/presentation/auth_ui_support.dart`) im weiten Layout
|
||
(`wideScreenSize`); E2E klickt per `getByRole('button', …)`.
|
||
|
||
Legende der Skizzen: `[ ]` Feld, `[x]` Checkbox, `( )` Primärknopf, `<…>` Link/TextButton, `!` Banner.
|
||
|
||
## 2. Skizzen
|
||
|
||
### RegisterScreen (`/register`)
|
||
|
||
```
|
||
Konto erstellen (Titel)
|
||
! Fehler-Banner (nur bei registrationRejected / network / rateLimited / unexpected)
|
||
E-Mail
|
||
[ name@example.com ] Fehler: authEmailInvalid
|
||
Passwort
|
||
[ •••••••••• (Auge) ] Hinweis: authPasswordRule / Fehler je Regel
|
||
Anzeigename
|
||
[ Alex 4/30 ] Fehler: authDisplayNameEmpty | TooLong | Invalid
|
||
[x] Ich bin mindestens 16 Jahre alt Fehler: authAgeNotConfirmed
|
||
[x] Ich habe die <Datenschutzerklärung> gelesen und stimme den <Nutzungsbedingungen> zu
|
||
Fehler: authTermsNotAccepted
|
||
( Konto erstellen )
|
||
<Schon ein Konto? Anmelden>
|
||
```
|
||
|
||
### CheckEmailScreen (`/register/check-email`, Modus Bestätigung bzw. Reset)
|
||
|
||
```
|
||
Prüfe dein Postfach (Titel)
|
||
Wir haben dir eine E-Mail geschickt, falls ... (Text, bodyLarge)
|
||
Der Link ist 1 Stunde gültig. Die E-Mail kann einige Minuten dauern. Schau auch im Spam-Ordner nach.
|
||
! Info-Banner "E-Mail wurde erneut gesendet" (nach Resend, auch bei emailSendLimited)
|
||
! Fehler-Banner bei rateLimited / network
|
||
(nur wenn E-Mail fehlt, z. B. nach Reload:) E-Mail [ ]
|
||
( E-Mail erneut senden ) -> nur Modus Bestätigung, gesperrt solange Anfrage läuft
|
||
<Zurück zur Anmeldung> (Modus Reset: statt des Resend-Knopfs <Andere E-Mail-Adresse eingeben> -> /forgot-password)
|
||
```
|
||
|
||
Der Modus steckt im **Pfad**, nicht im `extra`: `/register/check-email` (Bestätigung, E-Mail als `extra`) und
|
||
`/forgot-password/check-email` (Reset, ohne `extra`). So überlebt der Modus einen Reload im Web. Im Modus Reset gibt es
|
||
keinen Resend (Spec § 5.4), Text `authCheckEmailBodyReset`, Gültigkeit und Zusatz „kann einige Minuten dauern“ wie im
|
||
Modus Bestätigung; Keys `check_email_different_address` (statt `check_email_resend`).
|
||
|
||
### LoginScreen (`/login`, umgesetzt in 01b)
|
||
|
||
```
|
||
Anmelden
|
||
! Fehler-Banner: authErrorInvalidCredentials | authErrorRateLimited | authErrorNetwork (+ "Erneut versuchen")
|
||
! Info-Banner authErrorEmailNotConfirmed + Aktion "Bestätigungs-E-Mail erneut senden"
|
||
! Info-Banner "Wir haben die E-Mail erneut gesendet ..." | Fehler-Banner (authErrorRateLimited / authErrorNetwork) nach dem Resend
|
||
E-Mail [ ] Fehler (nur lokal): authErrorEmailInvalid
|
||
Passwort [ ••••• (Auge) ] Fehler (nur lokal): authPasswordRequired
|
||
( Anmelden )
|
||
<Passwort vergessen?> -> /forgot-password
|
||
<Konto erstellen> -> /register
|
||
```
|
||
|
||
Umsetzungsentscheidungen:
|
||
|
||
- Falsche Zugangsdaten stehen nie am Feld, sondern nur im Banner; Text für falsche E-Mail und falsches Passwort
|
||
ist identisch (Krit. 7, Test vergleicht alle sichtbaren Texte). Felder zeigen nur lokale Fehler (leer / E-Mail-Syntax).
|
||
- Bei `emailNotConfirmed` merkt sich der Controller die E-Mail des Versuchs; "erneut senden" nutzt diese Adresse
|
||
(`ResendController`, `ResendOrigin.login`), nicht den aktuellen Feldinhalt. Dort wird `emailSendLimited` als
|
||
Drosselungshinweis gezeigt (F12). Während des Sendens ist die Aktion **deaktiviert, aber sichtbar** (`AppBanner.actionEnabled: false`):
|
||
kein Doppelversand, und der Fokus eines Screenreaders verschwindet nicht mit dem Knopf (Semantics: Knopf, nicht aktiviert).
|
||
- Ein neuer Anmeldeversuch setzt das Resend-Ergebnis zurück.
|
||
- `from` wird an „Konto erstellen“ und „Passwort vergessen?“ weitergegeben (`authLocation`, geprüft mit `safeFromPath`);
|
||
Register- und ForgotPassword-Screen geben es mit „Anmelden“ bzw. „Zurück zur Anmeldung“ an `/login` zurück.
|
||
- Erfolg: `context.go(<from oder />)`. `from` wird mit `safeFromPath` geprüft (nur interne Pfade, keine Auth-Seiten,
|
||
kein `//`, `/\` oder Schema); sonst Startseite. Zusätzlich leitet der Router bei Status `signedIn` von `/login` um.
|
||
- Autofill: `AutofillGroup`, E-Mail `username` + `email`, Passwort `password`; `TextInput.finishAutofillContext()` bei Erfolg.
|
||
- Neue l10n-Schlüssel: `authLoginTitle`, `authLoginSubmit`, `authLoginForgotPassword`, `authLoginCreateAccount`.
|
||
- Widget-Keys: `login_screen`, `login_email`, `login_password`, `login_submit`, `login_banner`,
|
||
`login_not_confirmed_banner`, `login_resent_banner`, `login_resend_error_banner`, `login_forgot_link`, `login_register_link`.
|
||
|
||
### ForgotPasswordScreen (`/forgot-password`)
|
||
|
||
```
|
||
Passwort zurücksetzen
|
||
Gib die E-Mail-Adresse deines Kontos ein. Wir senden dir einen Link zum Zurücksetzen.
|
||
E-Mail [ ] Fehler: authEmailInvalid
|
||
( Link senden )
|
||
<Zurück zur Anmeldung> -> /login (mit from)
|
||
```
|
||
|
||
Umsetzung: Nach jedem 200 **und** bei `emailSendLimited` (F12) geht es ohne Hinweis zu `/forgot-password/check-email`
|
||
(Antwort für bekannte und unbekannte Adresse sowie für die Mail-Sperre ist identisch, getestet per exaktem
|
||
Textvergleich). Sichtbare Fehler nur für lokale Syntax, `invalidEmail` (am Feld), `rateLimited`/`network`/`unexpected`
|
||
(Banner, bei den beiden letzten mit „Erneut versuchen“, während des Sendens deaktiviert). Die Adresse wird nicht an
|
||
die Folgeseite übergeben. Keys: `forgot_password_screen`, `forgot_password_email`, `forgot_password_submit`,
|
||
`forgot_password_banner`, `forgot_password_body`, `forgot_password_back`.
|
||
|
||
### EmailLinkScreen (`/auth/confirm`)
|
||
|
||
Zustände: `idle` (Knopf), `submitting`, `success` (nur Bestätigung), `error` (Link ungültig).
|
||
|
||
```
|
||
E-Mail-Adresse bestätigen | Neues Passwort festlegen (Titel je Typ)
|
||
Ein Klick genügt, um fortzufahren.
|
||
( E-Mail-Adresse bestätigen ) | ( Weiter zum neuen Passwort )
|
||
|
||
Erfolg: ! Info-Banner "E-Mail bestätigt" (+ Mobile-Web: "Du kannst dich jetzt auch in der App anmelden.")
|
||
Fehler: ! Fehler-Banner "Der Link ist abgelaufen ..." ( Neuen Link anfordern )
|
||
```
|
||
|
||
Typ Recovery (01c): Titel, Text und Knopf aus `authRecovery*`; das Einlösen passiert erst per Klick. Bei Erfolg zeigt die
|
||
Seite nichts weiter an, der Statuswechsel `passwordRecovery` leitet nach `/reset-password`. Ungültiger oder abgelaufener
|
||
Link (`otp_expired`) → Fehlerzustand mit „Neuen Link anfordern“ → `/forgot-password` (Bestätigung: `/login`).
|
||
|
||
### ResetPasswordScreen (`/reset-password`)
|
||
|
||
```
|
||
Neues Passwort festlegen
|
||
Neues Passwort
|
||
[ ••••••••• (Auge) ] Live-Regeln: authPasswordRule, Fehler je Regel / samePassword
|
||
( Passwort speichern )
|
||
<Abbrechen> -> signOut -> /login
|
||
|
||
Erfolg (Ansicht ersetzt das Formular, kein Abbrechen mehr):
|
||
Passwort geändert. (Hinweis authPasswordChanged)
|
||
( Weiter ) -> /
|
||
```
|
||
|
||
Umsetzungsentscheidungen (01c):
|
||
|
||
- **Der Redirect nach `userUpdated` wäre ohne Eingriff ein Fehler:** gotrue liefert `userUpdated` asynchron (auch nach der Rückkehr von `updateUser`); die Regel „angemeldet auf `/reset-password` → `/`“ hätte die Seite entfernt, bevor die Erfolgsmeldung
|
||
(Krit. 9) sichtbar ist. Lösung: `userUpdated` während der Recovery beendet den Guard **nicht**, sondern setzt
|
||
`AuthStatus.passwordRecovery(passwordChanged: true)`. Das Repository markiert die Änderung nach erfolgreichem `updateUser` zusätzlich selbst (idempotent), das Formular bleibt
|
||
bis zur Erfolgsansicht gesperrt. Erst „Weiter“ (`AuthRepository.finishPasswordRecovery()`, vor dem Wechsel ohne Wirkung) beendet den
|
||
Guard; der Fokus liegt beim Wechsel auf „Weiter“; der Router leitet dann nach `/`. Die Erfolgsansicht leitet sich aus dem Status ab (`passwordRecoveryCompleted`),
|
||
überlebt also einen Neuaufbau der Seite. Bewusst **kein** zeitgesteuerter Sprung (Screenreader, Lesezeit). Spec § 5.5
|
||
ist entsprechend ergänzt.
|
||
- „Abbrechen“ gibt es nur im Formular, nicht in der Erfolgsansicht (es würde eine gültige Sitzung abmelden).
|
||
- Passwortregel wie bei der Registrierung (10 Zeichen, Liste häufiger Passwörter; die Liste wird vor dem Absenden
|
||
geladen, Fallback nur Länge). `weak_password` und `same_password` stehen am Feld, alles andere im Banner.
|
||
- Hinweistext ehrlich zu F1: andere Geräte können bis zu etwa einer Stunde angemeldet bleiben; nichts von „sofort“.
|
||
- Keys: `reset_password_screen`, `reset_password_password`, `reset_password_submit`, `reset_password_cancel`,
|
||
`reset_password_banner`, `reset_password_success_banner`, `reset_password_continue`.
|
||
|
||
## 3. Textvorschläge
|
||
|
||
Ton: du-Form, kurz, sachlich-freundlich, keine Schuldzuweisung. Platzhalter in ICU-Syntax (`{n}`).
|
||
Neutrale Texte (Krit. 4, F12) verraten nie, ob eine Adresse registriert ist.
|
||
|
||
### 3.1 Meldungen aus § 4.4 (Fehlerabbildung)
|
||
|
||
| Schlüssel | de | en |
|
||
|---|---|---|
|
||
| `authErrorInvalidCredentials` | E-Mail-Adresse oder Passwort ist nicht korrekt. | Email address or password is incorrect. |
|
||
| `authErrorEmailNotConfirmed` | Bestätige zuerst deine E-Mail-Adresse. Wir haben dir dazu eine E-Mail geschickt. | Please confirm your email address first. We sent you an email for this. |
|
||
| `authActionResendConfirmation` | Bestätigungs-E-Mail erneut senden | Resend confirmation email |
|
||
| `authErrorRateLimited` | Zu viele Versuche. Bitte warte einige Minuten und versuche es erneut. | Too many attempts. Please wait a few minutes and try again. |
|
||
| `authErrorWeakPasswordLength` | Das Passwort ist zu kurz. | The password is too short. |
|
||
| `authErrorWeakPasswordCharacters` | Das Passwort ist zu einfach. Wähle ein anderes. | The password is too simple. Choose a different one. |
|
||
| `authErrorWeakPasswordPwned` | Dieses Passwort ist sehr verbreitet und leicht zu erraten. Wähle ein anderes. | This password is very common and easy to guess. Choose a different one. |
|
||
| `authErrorSamePassword` | Wähle ein anderes als dein bisheriges Passwort. | Choose a password different from your current one. |
|
||
| `authErrorLinkInvalidOrExpired` | Der Link ist abgelaufen oder wurde schon benutzt. | The link has expired or has already been used. |
|
||
| `authActionRequestNewLink` | Neuen Link anfordern | Request a new link |
|
||
| `authErrorEmailInvalid` | Gib eine gültige E-Mail-Adresse ein. | Enter a valid email address. |
|
||
| `authErrorRegistrationRejected` | Registrierung nicht möglich. Bitte prüfe deine Angaben. | Registration is not possible. Please check your details. |
|
||
| `authErrorNetwork` | Keine Verbindung. Für diesen Schritt brauchst du Internet. | No connection. You need internet access for this step. |
|
||
| `authErrorUnexpected` | Etwas ist schiefgelaufen. Bitte versuche es erneut. | Something went wrong. Please try again. |
|
||
| `authActionRetry` | Erneut versuchen | Try again |
|
||
|
||
`emailSendLimited` hat bewusst **keinen** eigenen Text: Register/Forgot/CheckEmail zeigen den neutralen Erfolgstext
|
||
(§ 3.3), nur der Resend im LoginScreen zeigt `authErrorRateLimited`.
|
||
|
||
### 3.2 Meldungen aus § 5.4 (Felder, Banner, Hinweise)
|
||
|
||
| Schlüssel | de | en |
|
||
|---|---|---|
|
||
| `authPasswordTooShort` | Mindestens 10 Zeichen – {missing, plural, =1{es fehlt noch 1 Zeichen} other{es fehlen noch {missing} Zeichen}}. | At least 10 characters – {missing, plural, =1{1 more character needed} other{{missing} more characters needed}}. |
|
||
| `authPasswordCommon` | Dieses Passwort ist sehr verbreitet und leicht zu erraten. Wähle ein anderes. | This password is very common and easy to guess. Choose a different one. |
|
||
| `authPasswordRule` | Mindestens 10 Zeichen. Kein sehr verbreitetes Passwort. | At least 10 characters. Not a very common password. |
|
||
| `authPasswordRequired` | Gib ein Passwort ein. | Enter a password. |
|
||
| `authDisplayNameEmpty` | Gib einen Anzeigenamen ein. | Enter a display name. |
|
||
| `authDisplayNameTooLong` | Der Anzeigename darf höchstens {max} Zeichen lang sein. | The display name can be at most {max} characters long. |
|
||
| `authDisplayNameInvalid` | Dieser Anzeigename ist nicht erlaubt. Verwende sichtbare Zeichen. | This display name is not allowed. Use visible characters. |
|
||
| `authAgeNotConfirmed` | Shuffle ist erst ab 16 Jahren nutzbar. | Shuffle can only be used from age 16. |
|
||
| `authTermsNotAccepted` | Bitte stimme Datenschutzerklärung und Nutzungsbedingungen zu. | Please agree to the privacy policy and the terms of use. |
|
||
| `authFieldEmailLabel` | E-Mail-Adresse | Email address |
|
||
| `authFieldPasswordLabel` | Passwort | Password |
|
||
| `authFieldNewPasswordLabel` | Neues Passwort | New password |
|
||
| `authFieldDisplayNameLabel` | Anzeigename | Display name |
|
||
| `authPasswordShow` | Passwort anzeigen | Show password |
|
||
| `authPasswordHide` | Passwort verbergen | Hide password |
|
||
| `authCheckboxAgeLabel` | Ich bin mindestens 16 Jahre alt | I am at least 16 years old |
|
||
| `authCheckboxTermsPrefix` | Ich habe die | I have read the |
|
||
| `authCheckboxTermsPrivacyLink` | Datenschutzerklärung | privacy policy |
|
||
| `authCheckboxTermsMiddle` | gelesen und stimme den | and agree to the |
|
||
| `authCheckboxTermsTermsLink` | Nutzungsbedingungen | terms of use |
|
||
| `authCheckboxTermsSuffix` | zu | (leer) |
|
||
| `authBannerTypeError` | Fehler | Error |
|
||
| `authBannerTypeInfo` | Hinweis | Note |
|
||
| `authSubmitting` | Bitte warten … | Please wait … |
|
||
|
||
Hinweis zur Checkbox „Nutzungsbedingungen“: Satzbau ist je Sprache verschieden. Der Satz wird deshalb aus den
|
||
Segmenten `Prefix`, `PrivacyLink`, `Middle`, `TermsLink`, `Suffix` zusammengesetzt (`AppCheckboxField.rich`);
|
||
im Englischen bleibt `Suffix` leer. Alternativ ein ICU-Satz mit Platzhaltern, den der Screen an den Linkstellen
|
||
teilt – Entscheidung beim flutter-dev (A2).
|
||
|
||
### 3.3 Screen-Texte
|
||
|
||
| Schlüssel | de | en |
|
||
|---|---|---|
|
||
| `authRegisterTitle` | Konto erstellen | Create account |
|
||
| `authRegisterSubmit` | Konto erstellen | Create account |
|
||
| `authRegisterHaveAccount` | Schon ein Konto? Anmelden | Already have an account? Sign in |
|
||
| `authCheckEmailTitle` | Prüfe dein Postfach | Check your inbox |
|
||
| `authCheckEmailBody` | Wir haben dir eine E-Mail geschickt, falls die Adresse noch nicht registriert ist. Folge dem Link darin, um fortzufahren. | We sent you an email if the address is not registered yet. Follow the link in it to continue. |
|
||
| `authCheckEmailBodyReset` | Wir haben dir eine E-Mail mit einem Link zum Zurücksetzen geschickt, falls zu dieser Adresse ein Konto besteht. | We sent you an email with a reset link if an account exists for this address. |
|
||
| `authCheckEmailDelayHint` | Die E-Mail kann einige Minuten dauern. Schau auch im Spam-Ordner nach. | The email can take a few minutes. Also check your spam folder. |
|
||
| `authCheckEmailValidity` | Der Link ist {duration} gültig. | The link is valid for {duration}. |
|
||
| `authCheckEmailValidityOneHour` | 1 Stunde | 1 hour |
|
||
| `authCheckEmailResend` | E-Mail erneut senden | Resend email |
|
||
| `authCheckEmailResent` | Wir haben die E-Mail erneut gesendet, falls es zu dieser Adresse etwas zu senden gibt. | We resent the email, if there is something to send for this address. |
|
||
| `authCheckEmailDifferentAddress` | Andere E-Mail-Adresse eingeben | Enter a different email address |
|
||
| `authBackToLogin` | Zurück zur Anmeldung | Back to sign in |
|
||
| `authLoginTitle` | Anmelden | Sign in |
|
||
| `authLoginSubmit` | Anmelden | Sign in |
|
||
| `authLoginForgotPassword` | Passwort vergessen? | Forgot password? |
|
||
| `authLoginCreateAccount` | Konto erstellen | Create account |
|
||
| `authForgotTitle` | Passwort zurücksetzen | Reset password |
|
||
| `authForgotBody` | Gib die E-Mail-Adresse deines Kontos ein. Wir senden dir einen Link zum Zurücksetzen. | Enter the email address of your account. We will send you a reset link. |
|
||
| `authForgotSubmit` | Link senden | Send link |
|
||
| `authConfirmTitle` | E-Mail-Adresse bestätigen | Confirm email address |
|
||
| `authConfirmBody` | Ein Klick genügt, um deine E-Mail-Adresse zu bestätigen. | One click is enough to confirm your email address. |
|
||
| `authConfirmSubmit` | E-Mail-Adresse bestätigen | Confirm email address |
|
||
| `authConfirmSuccess` | E-Mail bestätigt. | Email confirmed. |
|
||
| `authConfirmSuccessMobileWeb` | Du kannst dich jetzt auch in der App anmelden. | You can now also sign in in the app. |
|
||
| `authRecoveryTitle` | Neues Passwort festlegen | Set a new password |
|
||
| `authRecoveryBody` | Ein Klick genügt, um ein neues Passwort festzulegen. | One click is enough to set a new password. |
|
||
| `authRecoverySubmit` | Weiter zum neuen Passwort | Continue to new password |
|
||
| `authResetTitle` | Neues Passwort festlegen | Set a new password |
|
||
| `authResetSubmit` | Passwort speichern | Save password |
|
||
| `authResetCancel` | Abbrechen | Cancel |
|
||
| `authPasswordChanged` | Passwort geändert. Andere Geräte werden abgemeldet; das kann bis zu einer Stunde dauern. | Password changed. Other devices will be signed out; this can take up to an hour. |
|
||
|
||
## 4. Barrierefreiheit pro Screen (Checkliste für A2–A4 und T2)
|
||
|
||
- Jedes Feld hat ein sichtbares Label (kein Platzhalter-Label); Fehler per `errorText` (Icon + Text + Live-Region).
|
||
- Banner und Fehler werden beim Erscheinen vorgelesen (Live-Region); keine reinen Farb-Signale.
|
||
- Passwortfelder: `AppPasswordField` mit `isNewPassword: true` bei Register/Reset, sonst `false`; E-Mail mit
|
||
`AutofillHints.email` (Login: `AutofillHints.username`/`email`), `TextInputType.emailAddress`.
|
||
- Links in Checkbox-Sätzen (`AppCheckboxField.rich`) sind eigene Semantics-Knoten (`link: true`, Label = Linktext,
|
||
Tap), keine Custom Actions (Flutter-Web setzt diese nicht in ARIA um). Der Checkbox-Knoten trägt den ganzen Satz.
|
||
- Meldungen, die sich bei jedem Tastendruck ändern („es fehlen noch n“), sind während des Tippens keine Live-Region
|
||
(`liveError: false`) und werden beim Verlassen des Feldes angesagt; der sichtbare Text bleibt.
|
||
- Alle interaktiven Elemente >= 48 dp, Tab-Reihenfolge wie Lesereihenfolge, Fokus sichtbar.
|
||
- Test je Screen mit `textScaler` 2.0 (Android 320 x 640), `androidTapTargetGuideline`,
|
||
`labeledTapTargetGuideline`, `textContrastGuideline`, Hell und Dunkel (T2).
|
||
|
||
## 3. Anmelde-Hinweis auf der Startseite (`SignInPrompt`, Spec #49, PR A)
|
||
|
||
Karte auf der Startseite, nur im Status `signedOut` (Entscheidung in `showSignInPromptProvider`; Laden: kein Hinweis,
|
||
Stream-Fehler: Hinweis). Auf allen Plattformen inklusive Web.
|
||
|
||
```
|
||
Dein Konto (Überschrift, titleLarge, Semantics header)
|
||
Melde dich an, um deine Sammlung und Partien zu verwalten. (bodyMedium)
|
||
( Anmelden ) -> /login (AppPrimaryButton, volle Breite)
|
||
( Konto erstellen ) -> /register (OutlinedButton, volle Breite)
|
||
```
|
||
|
||
- Max. 480 dp breit (`AppBreakpoints.authCardMaxWidth`), zentriert unter dem Platzhaltertext; Innenabstand 24 dp, Knopfabstand 12 dp.
|
||
- Karteninhalt in `Semantics(explicitChildNodes: true)` (Regel aus Issue #41): Überschrift, Text und beide Knöpfe sind eigene Knoten.
|
||
- Lese- und Tab-Reihenfolge: Überschrift, Text, „Anmelden“, „Konto erstellen“. Kein `from`-Parameter.
|
||
- l10n: `signInPromptTitle`, `signInPromptBody`, `signInPromptSignIn`, `signInPromptRegister`.
|
||
- Keys: `sign_in_prompt`, `sign_in_prompt_title`, `sign_in_prompt_body`, `sign_in_prompt_sign_in`, `sign_in_prompt_register`.
|