From f700000e87d4ab2d5ffa7bb7c9671d5e3cf5d90c Mon Sep 17 00:00:00 2001 From: neodk2004 Date: Mon, 7 Sep 2026 21:16:31 +0200 Subject: [PATCH] fix(mobile): dark mode + nested-scroll fix in pet creation, invalidate pets.list after create MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - PetCreateForm.tsx and onboarding/pet.tsx used the static light-only colors/typography tokens instead of useTheme() — pet creation stayed light even in dark mode. - onboarding/pet.tsx wrapped its steps in a plain ScrollView; the breed OptionPicker nests its own vertical ScrollView, and two same-axis ScrollViews swallow the inner one's scroll gesture on Android even with nestedScrollEnabled. Switched the outer container to a FlatList (empty data + ListHeaderComponent), matching explore.tsx's already working fix for the same component. - pets.create's mutation never invalidated trpc.pets.list, so ActivePetContext kept seeing zero pets after the first pet was created — OnboardingRedirect then bounced the user straight back to /onboarding/pet after the avatar step, with only an app restart breaking the loop. Added the missing invalidateQueries call. --- mobile/app/onboarding/pet.tsx | 23 ++++++++--- mobile/found.md | 9 +++++ .../components/onboarding/PetCreateForm.tsx | 40 +++++++++++++------ 3 files changed, 54 insertions(+), 18 deletions(-) diff --git a/mobile/app/onboarding/pet.tsx b/mobile/app/onboarding/pet.tsx index ea064a9..de507ef 100644 --- a/mobile/app/onboarding/pet.tsx +++ b/mobile/app/onboarding/pet.tsx @@ -1,5 +1,5 @@ import { useState } from "react"; -import { ScrollView } from "react-native"; +import { FlatList } from "react-native"; import { useRouter } from "expo-router"; import { useActivePet } from "../../src/context/ActivePetContext"; import { OnboardingGuidelines } from "../../src/components/onboarding/OnboardingGuidelines"; @@ -7,7 +7,7 @@ import { UploadConsent } from "../../src/components/onboarding/UploadConsent"; import { ArachnophobiaOptIn } from "../../src/components/onboarding/ArachnophobiaOptIn"; import { PetCreateForm } from "../../src/components/onboarding/PetCreateForm"; import { AvatarStep } from "../../src/components/onboarding/AvatarStep"; -import { colors } from "../../src/theme/tokens"; +import { useTheme } from "../../src/context/ThemeContext"; /** * Port of web's src/app/onboarding/pet/page.tsx — same five-step flow and @@ -18,6 +18,7 @@ import { colors } from "../../src/theme/tokens"; */ export default function OnboardingPetScreen() { const router = useRouter(); + const { colors } = useTheme(); const { setActivePetId } = useActivePet(); const [guidelinesAcknowledged, setGuidelinesAcknowledged] = useState(false); const [uploadConsentGiven, setUploadConsentGiven] = useState(false); @@ -47,8 +48,20 @@ export default function OnboardingPetScreen() { } return ( - - {content} - + // FlatList, not ScrollView, as the scroll container: PetCreateForm's + // Rassen-Dropdown (OptionPicker) nests its own vertical ScrollView, and + // two plain same-axis ScrollViews stacked inside each other silently eat + // the inner one's scroll gestures on Android (nestedScrollEnabled does + // not reliably fix real-ScrollView-in-ScrollView nesting) — a FlatList + // outer container is exactly explore.tsx's already-working fix for the + // same OptionPicker component (see its ListHeaderComponent usage there). + {content}} + /> ); } + +const EMPTY_DATA: never[] = []; diff --git a/mobile/found.md b/mobile/found.md index 18b12a9..12e4e9b 100644 --- a/mobile/found.md +++ b/mobile/found.md @@ -39,4 +39,13 @@ TODO Neu2: - [x] im Feed fehlt ein Indikator wenn in einem Post mehrere Bilder hinterlegt sind. das wischen funktioniert, aber nur wenn man weiß das dort mehrere bilder hinterlegt sind. unwissende würde nur ein bild sehen können. → Zähler-Badge oben rechts ("1 / 3") plus Punkte-Indikator unter dem Bild ergänzt (analog zur Web-Version), live bestätigt. - [x] Kommentar Fenster ist nicht im Darkmode +- [x] Tier Anlegen ist nicht im Darkmode + Rassen werden als Dropdown angezeigt lassen sich aber nicht scrollen. + → Darkmode: PetCreateForm.tsx + onboarding/pet.tsx nutzten noch die statischen (Hell-only) `colors`/`typography` statt `useTheme()` — umgestellt auf das gleiche Muster wie sign-up.tsx/OptionPicker.tsx. Scroll-Bug: onboarding/pet.tsx nutzte als äußeren Container ein normales `ScrollView`, darin verschachtelt der Rassen-Dropdown (OptionPicker) sein eigenes `ScrollView` — zwei gleichachsige ScrollViews ineinander verschlucken auf Android die Scroll-Geste des inneren, auch mit `nestedScrollEnabled`. Fix: äußeren Container auf `FlatList` (leere Liste + `ListHeaderComponent`) umgestellt, exakt das bereits funktionierende Muster aus explore.tsx für dieselbe OptionPicker-Komponente. Typecheck + Lint sauber, i18n-Parität unverändert (243/243) — nicht live auf Android getestet (kein Emulator in dieser Session), nur strukturell gegen den bekannten Explore-Fix abgeglichen. +- [x] Nach dem Speichern des ersten Tiers, und dem Hochladen des Profilfotos, wird man wieder zurück geworfen auf Tier erstellen. erst ein neustart der app lässt einen auf den feed zugreifen. (BUG) + → Ursache: `pets.create`-Mutation in PetCreateForm.tsx hat die `trpc.pets.list`-Query (ActivePetContext) nie invalidiert. Nach dem Avatar-Schritt landet man in `(app)`, aber `OnboardingRedirect` (app/_layout.tsx) sah dort weiterhin den alten, leeren Pets-Stand aus dem Cache und schickte sofort wieder zurück nach `/onboarding/pet` — nur ein Neustart hat die Liste frisch geladen. Fix: `queryClient.invalidateQueries` auf `trpc.pets.list` direkt im `onSuccess` der Create-Mutation. Typecheck sauber. videos die angeklickt werden, laufen in endlosschleife weiter obwohl man im feed weiter scrollt + - Profilbilder sind nicht vergrößerbar + - Profilbearbeiten, lässt sich die Rasse nicht mehr ändern; wäre wichtig wenn man bei der erstellung fehler gemacht hat. + - sowie kann man beim Tier kein geburtstag hinterlegen oder adoptionstag wie es im web vorhanden ist. + - Meilensteine sind nicht im Darkmode verfügbar + - - \ No newline at end of file diff --git a/mobile/src/components/onboarding/PetCreateForm.tsx b/mobile/src/components/onboarding/PetCreateForm.tsx index dd1c69f..56e5cbf 100644 --- a/mobile/src/components/onboarding/PetCreateForm.tsx +++ b/mobile/src/components/onboarding/PetCreateForm.tsx @@ -1,12 +1,13 @@ -import { useState } from "react"; +import { useMemo, useState } from "react"; import { View, Text, StyleSheet } from "react-native"; import { useTranslation } from "react-i18next"; -import { useMutation, useQuery } from "@tanstack/react-query"; +import { useMutation, useQuery, useQueryClient } from "@tanstack/react-query"; import { useTRPC } from "../../api/trpc"; import { TextField } from "../TextField"; import { Button } from "../Button"; import { OptionPicker, type PickerOption } from "../OptionPicker"; -import { colors, typography } from "../../theme/tokens"; +import { useTheme } from "../../context/ThemeContext"; +import { getTypography, type ColorTokens } from "../../theme/tokens"; type Props = { onSuccess: (pet: { id: string; name: string }) => void }; @@ -19,6 +20,9 @@ type Props = { onSuccess: (pet: { id: string; name: string }) => void }; export function PetCreateForm({ onSuccess }: Props) { const { t } = useTranslation(); const trpc = useTRPC(); + const queryClient = useQueryClient(); + const { colors } = useTheme(); + const styles = useMemo(() => makeStyles(colors), [colors]); const [name, setName] = useState(""); const [nickname, setNickname] = useState(""); const [speciesId, setSpeciesId] = useState(null); @@ -31,7 +35,14 @@ export function PetCreateForm({ onSuccess }: Props) { const createMutation = useMutation( trpc.pets.create.mutationOptions({ - onSuccess: (pet) => onSuccess({ id: pet.id, name: pet.name }), + onSuccess: (pet) => { + // Ohne dieses Invalidate bleibt ActivePetContext.pets (trpc.pets.list) + // auf dem alten "keine Tiere"-Stand stehen, bis die App neu startet — + // OnboardingRedirect wirft dann sofort wieder auf /onboarding/pet + // zurück, sobald man nach dem Avatar-Schritt in (app) landet. + queryClient.invalidateQueries({ queryKey: trpc.pets.list.queryOptions().queryKey }); + onSuccess({ id: pet.id, name: pet.name }); + }, onError: (err) => setError(err.message), }) ); @@ -161,12 +172,15 @@ function BreedPicker({ ); } -const styles = StyleSheet.create({ - container: { padding: 24, gap: 6 }, - title: { ...typography.title, fontSize: 18, marginBottom: 4 }, - subtitle: { ...typography.body, color: colors.quietGray, marginBottom: 16 }, - label: { ...typography.label, textTransform: "none", letterSpacing: 0, marginTop: 10 }, - multiline: { height: 80, textAlignVertical: "top", paddingTop: 12 }, - error: { ...typography.body, color: colors.alertRed, marginTop: 8 }, - cta: { marginTop: 16 }, -}); +function makeStyles(colors: ColorTokens) { + const typography = getTypography(colors); + return StyleSheet.create({ + container: { padding: 24, gap: 6 }, + title: { ...typography.title, fontSize: 18, marginBottom: 4 }, + subtitle: { ...typography.body, color: colors.quietGray, marginBottom: 16 }, + label: { ...typography.label, textTransform: "none", letterSpacing: 0, marginTop: 10 }, + multiline: { height: 80, textAlignVertical: "top", paddingTop: 12 }, + error: { ...typography.body, color: colors.alertRed, marginTop: 8 }, + cta: { marginTop: 16 }, + }); +}