ForgotPasswordScreen with neutral reaction (F12), CheckEmail reset mode on its own path, recovery link handling and ResetPasswordScreen with the recovery guard held until the user confirms the success message. Carries the from parameter between login, register and forgot password. Removes the route placeholders. Refs #1 Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
272 lines
19 KiB
Markdown
272 lines
19 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`.
|
||
|
||
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 sendet `userUpdated`, bevor `updateUser`
|
||
zurückkehrt; 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)`. Erst „Weiter“ (`AuthRepository.finishPasswordRecovery()`) beendet den
|
||
Guard; 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. Auf anderen Geräten wirst du spätestens nach etwa einer Stunde abgemeldet. | Password changed. On other devices you will be signed out after about an hour at the latest. |
|
||
|
||
## 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).
|