diff --git a/mobile/app/(app)/search.tsx b/mobile/app/(app)/search.tsx index 4ba02df..9d82282 100644 --- a/mobile/app/(app)/search.tsx +++ b/mobile/app/(app)/search.tsx @@ -1,4 +1,4 @@ -import { useState } from "react"; +import { useMemo, useState } from "react"; import { View, Text, Pressable, FlatList, ActivityIndicator, StyleSheet } from "react-native"; import { useRouter } from "expo-router"; import { useTranslation } from "react-i18next"; @@ -7,14 +7,16 @@ import { Search as SearchIcon, Layers2 } from "lucide-react-native"; import { Image } from "expo-image"; import { useTRPC } from "../../src/api/trpc"; import { useActivePet } from "../../src/context/ActivePetContext"; +import { useTheme } from "../../src/context/ThemeContext"; import { useDebounce } from "../../src/hooks/useDebounce"; import { TextField } from "../../src/components/TextField"; import { PetAvatar } from "../../src/components/PetAvatar"; import { FollowButton } from "../../src/components/FollowButton"; import { getMediaUrl } from "../../src/lib/media-url"; -import { colors, typography, radii } from "../../src/theme/tokens"; +import { radii, getTypography, type ColorTokens } from "../../src/theme/tokens"; type Tab = "pets" | "people" | "tags"; +type Styles = ReturnType; type SearchPet = { id: string; @@ -35,6 +37,8 @@ export default function SearchScreen() { const trpc = useTRPC(); const router = useRouter(); const { activePetId } = useActivePet(); + const { colors } = useTheme(); + const styles = useMemo(() => makeStyles(colors), [colors]); const [query, setQuery] = useState(""); const [tab, setTab] = useState("pets"); @@ -70,39 +74,39 @@ export default function SearchScreen() { - setTab("pets")} /> - setTab("people")} /> - setTab("tags")} /> + setTab("pets")} /> + setTab("people")} /> + setTab("tags")} /> {tab === "pets" && (!enabled ? ( - + ) : loadingPets ? ( - + ) : ( pet.id} contentContainerStyle={styles.list} - ListEmptyComponent={} + ListEmptyComponent={} renderItem={({ item }) => ( - router.push(`/pets/${item.id}`)} /> + router.push(`/pets/${item.id}`)} /> )} /> ))} {tab === "people" && (!enabled ? ( - + ) : loadingPeople ? ( - + ) : ( owner.id} contentContainerStyle={styles.list} - ListEmptyComponent={} + ListEmptyComponent={} renderItem={({ item: owner }) => { const displayName = owner.username ? `@${owner.username}` @@ -113,6 +117,7 @@ export default function SearchScreen() { {owner.pets.map((pet) => ( router.push(`/pets/${pet.id}`)} @@ -127,16 +132,16 @@ export default function SearchScreen() { {tab === "tags" && (normalizedTag.length < 1 ? ( - + ) : loadingTags ? ( - + ) : ( post.id} numColumns={3} contentContainerStyle={styles.tagGrid} - ListEmptyComponent={} + ListEmptyComponent={} renderItem={({ item: post }) => { const firstImage = post.images[0]; const isCarousel = post.images.length > 1; @@ -166,7 +171,17 @@ export default function SearchScreen() { ); } -function TabButton({ label, active, onPress }: { label: string; active: boolean; onPress: () => void }) { +function TabButton({ + styles, + label, + active, + onPress, +}: { + styles: Styles; + label: string; + active: boolean; + onPress: () => void; +}) { return ( {label} @@ -175,11 +190,13 @@ function TabButton({ label, active, onPress }: { label: string; active: boolean; } function PetRow({ + styles, pet, activePetId, onPress, indented, }: { + styles: Styles; pet: SearchPet; activePetId: string | null; onPress: () => void; @@ -201,7 +218,7 @@ function PetRow({ ); } -function Loading() { +function Loading({ styles, colors }: { styles: Styles; colors: ColorTokens }) { return ( @@ -209,36 +226,40 @@ function Loading() { ); } -function EmptyState({ text }: { text: string }) { +function EmptyState({ styles, text }: { styles: Styles; text: string }) { return ( - {text} + {text} ); } -const styles = StyleSheet.create({ - container: { flex: 1, backgroundColor: colors.porchWhite }, - searchBar: { paddingHorizontal: 16, paddingTop: 12, justifyContent: "center" }, - searchIcon: { position: "absolute", left: 28, zIndex: 1 }, - searchInput: { paddingLeft: 36 }, - tabs: { flexDirection: "row", gap: 8, paddingHorizontal: 16, paddingVertical: 12 }, - tabButton: { paddingHorizontal: 14, height: 36, borderRadius: radii.lg, justifyContent: "center", backgroundColor: colors.whisperGray }, - tabButtonActive: { backgroundColor: colors.marigoldOrange }, - tabButtonText: { ...typography.label, textTransform: "none", letterSpacing: 0, color: colors.ink }, - tabButtonTextActive: { color: colors.pureWhite }, - list: { padding: 16, flexGrow: 1 }, - center: { flex: 1, alignItems: "center", justifyContent: "center", padding: 24 }, - petRow: { flexDirection: "row", alignItems: "center", gap: 10, paddingVertical: 8 }, - petRowIndented: { marginLeft: 16 }, - petRowMain: { flex: 1, flexDirection: "row", alignItems: "center", gap: 10 }, - petRowText: { flex: 1 }, - petName: { ...typography.body, fontFamily: typography.title.fontFamily }, - petSpecies: { ...typography.label, textTransform: "none", letterSpacing: 0 }, - ownerBlock: { marginBottom: 16 }, - ownerName: { ...typography.body, fontFamily: typography.title.fontFamily, marginBottom: 4 }, - tagGrid: { padding: 2 }, - tagTile: { flex: 1, aspectRatio: 1, padding: 2 }, - tagImage: { width: "100%", height: "100%", borderRadius: radii.sm }, - tagCarouselBadge: { position: "absolute", top: 6, right: 6 }, -}); +function makeStyles(colors: ColorTokens) { + const typography = getTypography(colors); + return StyleSheet.create({ + container: { flex: 1, backgroundColor: colors.porchWhite }, + searchBar: { paddingHorizontal: 16, paddingTop: 12, justifyContent: "center" }, + searchIcon: { position: "absolute", left: 28, zIndex: 1 }, + searchInput: { paddingLeft: 36 }, + tabs: { flexDirection: "row", gap: 8, paddingHorizontal: 16, paddingVertical: 12 }, + tabButton: { paddingHorizontal: 14, height: 36, borderRadius: radii.lg, justifyContent: "center", backgroundColor: colors.whisperGray }, + tabButtonActive: { backgroundColor: colors.marigoldOrange }, + tabButtonText: { ...typography.label, textTransform: "none", letterSpacing: 0, color: colors.ink }, + tabButtonTextActive: { color: colors.pureWhite }, + list: { padding: 16, flexGrow: 1 }, + center: { flex: 1, alignItems: "center", justifyContent: "center", padding: 24 }, + emptyText: { ...typography.body }, + petRow: { flexDirection: "row", alignItems: "center", gap: 10, paddingVertical: 8 }, + petRowIndented: { marginLeft: 16 }, + petRowMain: { flex: 1, flexDirection: "row", alignItems: "center", gap: 10 }, + petRowText: { flex: 1 }, + petName: { ...typography.body, fontFamily: typography.title.fontFamily }, + petSpecies: { ...typography.label, textTransform: "none", letterSpacing: 0 }, + ownerBlock: { marginBottom: 16 }, + ownerName: { ...typography.body, fontFamily: typography.title.fontFamily, marginBottom: 4 }, + tagGrid: { padding: 2 }, + tagTile: { flex: 1, aspectRatio: 1, padding: 2 }, + tagImage: { width: "100%", height: "100%", borderRadius: radii.sm }, + tagCarouselBadge: { position: "absolute", top: 6, right: 6 }, + }); +} diff --git a/mobile/found.md b/mobile/found.md index 1cf3dcd..3b0a825 100644 --- a/mobile/found.md +++ b/mobile/found.md @@ -123,5 +123,5 @@ Neu 7: - [x] Nutzer Feedback - DarkMode - Neuer Beitrag - Text die getippt werden sind weiß auf weißem grund. → Ursache gefunden: `app/(app)/post-new.tsx` gehörte noch zu den nicht auf Dark Mode migrierten Screens — es importierte die statischen, Light-only `colors`/`typography` aus `theme/tokens.ts` direkt, statt sie per `useTheme()` zu beziehen (gleiche Bug-Klasse wie die bereits behobenen Fälle aus dem Darkmode-Audit, siehe README/CLAUDE.md-Kommentar in `theme/tokens.ts`: "dark-mode support has to be added file-by-file"). Der Container-Hintergrund blieb dadurch immer hell (`colors.porchWhite`), während die geteilte `TextField`-Komponente **korrekt** theme-abhängige Textfarbe zieht (`colors.ink`, im Dark Mode hell) — macht bei aktivem Dark Mode aus hellem Text auf hellem Hintergrund den gemeldeten "weiß auf weiß"-Effekt in der Caption-Eingabe. Fix: `post-new.tsx` auf `useTheme()` + `makeStyles(colors)`-Muster umgestellt (identisch zu `milestone-new.tsx`). Gleiches Muster/gleicher Fund auch in `story-new.tsx` entdeckt (dort ohne Texteingabe, macht sich als falscher heller Hintergrund statt unlesbarem Text bemerkbar) — gleich mitbehoben. - **Noch offen, nicht mitbehoben (größerer Umbau nötig):** `app/(app)/search.tsx` hat denselben Fehler (Suchfeld nutzt `TextField` + statische Farben) — dort referenzieren mehrere separate Unterkomponenten (`TabButton`, `PetRow`, `Loading`, `EmptyState`) die statischen `colors`/`typography` direkt außerhalb der Hauptkomponente, bräuchte also eigenes `useTheme()` pro Unterkomponente statt nur den einen `makeStyles`-Umbau. Separater Fix, auf Rückmeldung ob gewünscht. - Typecheck + Lint sauber (0 neue Warnungen, 6 vorbestehende `exhaustive-deps`-Warnungen unverändert). \ No newline at end of file + Typecheck + Lint sauber (0 neue Warnungen, 6 vorbestehende `exhaustive-deps`-Warnungen unverändert). + **Nachtrag:** `app/(app)/search.tsx` (gleicher Fehler im Suchfeld) auf Nachfrage ebenfalls behoben — dort bekommen die separaten Unterkomponenten (`TabButton`, `PetRow`, `Loading`, `EmptyState`) `styles`/`colors` jetzt als Props von der Hauptkomponente durchgereicht, statt selbst die statischen Tokens zu importieren. Typecheck + Lint sauber (1 vorbestehende, unveränderte `alt-text`-Warnung bei `expo-image`). \ No newline at end of file