From d7b15e231a54ac588873eb6a19ea339d704a575b Mon Sep 17 00:00:00 2001 From: chahinebrini Date: Sat, 9 May 2026 14:51:02 +0200 Subject: [PATCH] =?UTF-8?q?feat(theme):=20Dark=20Mode=20Wave=202=20?= =?UTF-8?q?=E2=80=94=20blocker,=20mail,=20chat,=20community,=20notificatio?= =?UTF-8?q?ns,=20all=20remaining=20screens?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Wave 2 = ALLE app-files die in Wave 1 noch hardcoded waren. Komplette App-weit theme-aware-Migration jetzt durch. Legacy `import { colors }` flat export vollständig eliminiert. Migrated this wave: Top-level Screens: - app/urge.tsx (makeStyles factory mit ~20 colors) - app/room.tsx + dm.tsx + games.tsx - app/(app)/chat.tsx + mail.tsx + coach.tsx + notifications.tsx - app/profile/[userId].tsx + profile/edit.tsx (INPUT_STYLE in body moved) - app/debug.tsx + auth/callback.tsx Blocker (7): - AddDomainSheet, CooldownBanner, DeactivationExplainerSheet, DomainGrid, ProtectionCard, ProtectionDetailsSheet, ProtectionLockedCard Mail (3): - ConnectMailSheet, EditMailAccountSheet, MailEmptyState Chat (1): - ChatBubble, ChatInput Community/Posts/Notifications: - PostCard, PostCardSkeleton, ComposeCard, PostCommentsSheet - NotificationsDropdown - StreakBadge (Nativewind classes durch inline dynamic styles ersetzt) Reusable Sheets: - WheelPickerModal, OptionsBottomSheet, DeviceLimitReachedSheet Urge subsystem (5): - InlineRatingDrawer, ShareSuccessDrawer, UrgeStats, SosFeedbackModal, Breathing Profile components: - DigaMissionBanner Pattern: useColors() hook in component body, makeStyles(colors) factory wo StyleSheet.create vorher hardcoded war. 11 base-tokens (bg/surface/ surfaceElevated/border/text/textMuted/brandOrange/brandBlue/success/error/ warning) nutzen colors.light vs colors.dark scheme. Bewusst NICHT migriert (semantic colors): - DigaMissionBanner amber (#fffbeb, #854d0e) — DiGA-brand, nicht neutral - Lyra-thinking #3b82f6 in urge.tsx — Lyra-brand-color - scrollDownBtn #374151 — intentional dark floating-button TS clean. Test: Settings → Theme → Dark — alle screens sollen jetzt dunkel werden ohne white-flashes. Co-Authored-By: Claude Opus 4.7 (1M context) --- apps/rebreak-native/app/(app)/chat.tsx | 317 +++++++------ apps/rebreak-native/app/(app)/coach.tsx | 4 +- apps/rebreak-native/app/(app)/mail.tsx | 16 +- .../app/(app)/notifications.tsx | 25 +- apps/rebreak-native/app/auth/callback.tsx | 5 +- apps/rebreak-native/app/debug.tsx | 20 +- apps/rebreak-native/app/dm.tsx | 132 +++--- apps/rebreak-native/app/games.tsx | 19 +- apps/rebreak-native/app/profile/[userId].tsx | 28 +- apps/rebreak-native/app/profile/edit.tsx | 25 +- apps/rebreak-native/app/room.tsx | 448 +++++++++--------- apps/rebreak-native/app/urge.tsx | 80 ++-- .../rebreak-native/components/ComposeCard.tsx | 11 +- .../components/DeviceLimitReachedSheet.tsx | 6 +- .../components/NotificationsDropdown.tsx | 23 +- .../components/OptionsBottomSheet.tsx | 3 +- apps/rebreak-native/components/PostCard.tsx | 20 +- .../components/PostCardSkeleton.tsx | 20 +- .../components/PostCommentsSheet.tsx | 40 +- .../rebreak-native/components/StreakBadge.tsx | 12 +- .../components/WheelPickerModal.tsx | 7 +- .../components/blocker/AddDomainSheet.tsx | 30 +- .../components/blocker/CooldownBanner.tsx | 2 + .../blocker/DeactivationExplainerSheet.tsx | 23 +- .../components/blocker/DomainGrid.tsx | 24 +- .../components/blocker/ProtectionCard.tsx | 22 +- .../blocker/ProtectionDetailsSheet.tsx | 64 +-- .../blocker/ProtectionLockedCard.tsx | 17 +- .../components/chat/ChatBubble.tsx | 239 +++++----- .../components/chat/ChatInput.tsx | 206 ++++---- .../components/chat/CreateRoomSheet.tsx | 275 +++++------ .../components/chat/RoomCard.tsx | 233 ++++----- .../components/mail/ConnectMailSheet.tsx | 42 +- .../components/mail/EditMailAccountSheet.tsx | 22 +- .../components/mail/MailEmptyState.tsx | 12 +- .../components/profile/DigaMissionBanner.tsx | 3 +- .../components/urge/Breathing.tsx | 12 +- .../components/urge/InlineRatingDrawer.tsx | 21 +- .../components/urge/ShareSuccessDrawer.tsx | 14 +- .../components/urge/SosFeedbackModal.tsx | 21 +- .../components/urge/UrgeStats.tsx | 56 +-- 41 files changed, 1354 insertions(+), 1245 deletions(-) diff --git a/apps/rebreak-native/app/(app)/chat.tsx b/apps/rebreak-native/app/(app)/chat.tsx index 2d713c0..8c2209e 100644 --- a/apps/rebreak-native/app/(app)/chat.tsx +++ b/apps/rebreak-native/app/(app)/chat.tsx @@ -17,7 +17,7 @@ import { apiFetch } from '../../lib/api'; import { AppHeader } from '../../components/AppHeader'; import { RoomCard, type Room } from '../../components/chat/RoomCard'; import { CreateRoomSheet } from '../../components/chat/CreateRoomSheet'; -import { colors } from '../../lib/theme'; +import { useColors } from '../../lib/theme'; type DmConversation = { partnerId: string; @@ -39,6 +39,8 @@ function formatTime(ts: string, justNowLabel: string): string { function DmItem({ conv, onPress }: { conv: DmConversation; onPress: () => void }) { const { t } = useTranslation(); + const colors = useColors(); + const styles = makeStyles(colors); const hasUnread = conv.unreadCount > 0; return ( @@ -95,6 +97,8 @@ function DmItem({ conv, onPress }: { conv: DmConversation; onPress: () => void } export default function ChatScreen() { const { t } = useTranslation(); const router = useRouter(); + const colors = useColors(); + const styles = makeStyles(colors); const [tab, setTab] = useState<'groups' | 'direct'>('groups'); const [createOpen, setCreateOpen] = useState(false); @@ -199,13 +203,13 @@ export default function ChatScreen() { } ListEmptyComponent={ loadingRooms ? ( - + ) : ( @@ -225,13 +229,13 @@ export default function ChatScreen() { } ListEmptyComponent={ loadingDms ? ( - + ) : ( @@ -257,154 +261,155 @@ export default function ChatScreen() { ); } -const styles = StyleSheet.create({ - container: { flex: 1, backgroundColor: '#fafafa' }, - headerSection: { - paddingHorizontal: 16, - paddingTop: 14, - paddingBottom: 10, - backgroundColor: '#fff', - borderBottomWidth: StyleSheet.hairlineWidth, - borderBottomColor: '#e5e5e5', - }, - titleRow: { - flexDirection: 'row', - alignItems: 'center', - justifyContent: 'space-between', - }, - title: { - fontSize: 22, - fontFamily: 'Nunito_800ExtraBold', - color: '#171717', - }, - createBtn: { - width: 34, - height: 34, - borderRadius: 17, - backgroundColor: '#007AFF', - alignItems: 'center', - justifyContent: 'center', - }, - tabs: { - flexDirection: 'row', - marginTop: 12, - backgroundColor: '#f5f5f5', - borderRadius: 10, - padding: 3, - }, - tab: { - flex: 1, - flexDirection: 'row', - alignItems: 'center', - justifyContent: 'center', - paddingVertical: 7, - borderRadius: 8, - }, - tabActive: { - backgroundColor: '#fff', - shadowColor: '#000', - shadowOpacity: 0.05, - shadowRadius: 2, - shadowOffset: { width: 0, height: 1 }, - }, - tabText: { - fontSize: 12, - fontFamily: 'Nunito_600SemiBold', - color: '#737373', - marginLeft: 5, - }, - tabTextActive: { - color: '#007AFF', - fontFamily: 'Nunito_700Bold', - }, - tabBadge: { - minWidth: 16, - height: 16, - borderRadius: 8, - backgroundColor: '#007AFF', - paddingHorizontal: 4, - alignItems: 'center', - justifyContent: 'center', - marginLeft: 5, - }, - tabBadgeText: { - fontSize: 9, - fontFamily: 'Nunito_700Bold', - color: '#fff', - }, - emptyBox: { - alignItems: 'center', - justifyContent: 'center', - paddingVertical: 60, - paddingHorizontal: 32, - }, - emptyText: { - fontSize: 13, - fontFamily: 'Nunito_600SemiBold', - color: '#a3a3a3', - marginTop: 12, - }, - // DM row styles - dmRow: { - width: '100%', - flexDirection: 'row', - alignItems: 'center', - paddingHorizontal: 14, - paddingVertical: 11, - backgroundColor: '#fff', - borderBottomWidth: StyleSheet.hairlineWidth, - borderBottomColor: '#f5f5f5', - }, - dmAvatar: { - width: 42, - height: 42, - borderRadius: 21, - backgroundColor: '#e5e5e5', - alignItems: 'center', - justifyContent: 'center', - overflow: 'hidden', - marginRight: 10, - }, - dmAvatarImg: { width: 42, height: 42 }, - dmAvatarInitials: { - fontSize: 13, - fontFamily: 'Nunito_700Bold', - color: '#525252', - }, - dmInfo: { flex: 1, minWidth: 0 }, - dmHeaderRow: { - flexDirection: 'row', - alignItems: 'center', - justifyContent: 'space-between', - }, - dmName: { - fontSize: 14, - fontFamily: 'Nunito_700Bold', - color: '#171717', - flexShrink: 1, - marginRight: 6, - }, - dmTime: { fontSize: 11, fontFamily: 'Nunito_600SemiBold' }, - dmBottomRow: { - flexDirection: 'row', - alignItems: 'center', - justifyContent: 'space-between', - marginTop: 2, - }, - dmLast: { fontSize: 12, flex: 1 }, - unreadBadge: { - minWidth: 20, - height: 20, - paddingHorizontal: 6, - borderRadius: 10, - backgroundColor: '#007AFF', - alignItems: 'center', - justifyContent: 'center', - marginLeft: 8, - }, - unreadBadgeText: { - fontSize: 10, - fontFamily: 'Nunito_700Bold', - color: '#fff', - }, -}); +function makeStyles(colors: ReturnType) { + return StyleSheet.create({ + container: { flex: 1, backgroundColor: colors.bg }, + headerSection: { + paddingHorizontal: 16, + paddingTop: 14, + paddingBottom: 10, + backgroundColor: colors.bg, + borderBottomWidth: StyleSheet.hairlineWidth, + borderBottomColor: colors.border, + }, + titleRow: { + flexDirection: 'row', + alignItems: 'center', + justifyContent: 'space-between', + }, + title: { + fontSize: 22, + fontFamily: 'Nunito_800ExtraBold', + color: colors.text, + }, + createBtn: { + width: 34, + height: 34, + borderRadius: 17, + backgroundColor: '#007AFF', + alignItems: 'center', + justifyContent: 'center', + }, + tabs: { + flexDirection: 'row', + marginTop: 12, + backgroundColor: colors.surfaceElevated, + borderRadius: 10, + padding: 3, + }, + tab: { + flex: 1, + flexDirection: 'row', + alignItems: 'center', + justifyContent: 'center', + paddingVertical: 7, + borderRadius: 8, + }, + tabActive: { + backgroundColor: colors.surface, + shadowColor: '#000', + shadowOpacity: 0.05, + shadowRadius: 2, + shadowOffset: { width: 0, height: 1 }, + }, + tabText: { + fontSize: 12, + fontFamily: 'Nunito_600SemiBold', + color: colors.textMuted, + marginLeft: 5, + }, + tabTextActive: { + color: '#007AFF', + fontFamily: 'Nunito_700Bold', + }, + tabBadge: { + minWidth: 16, + height: 16, + borderRadius: 8, + backgroundColor: '#007AFF', + paddingHorizontal: 4, + alignItems: 'center', + justifyContent: 'center', + marginLeft: 5, + }, + tabBadgeText: { + fontSize: 9, + fontFamily: 'Nunito_700Bold', + color: '#fff', + }, + emptyBox: { + alignItems: 'center', + justifyContent: 'center', + paddingVertical: 60, + paddingHorizontal: 32, + }, + emptyText: { + fontSize: 13, + fontFamily: 'Nunito_600SemiBold', + color: colors.textMuted, + marginTop: 12, + }, + dmRow: { + width: '100%', + flexDirection: 'row', + alignItems: 'center', + paddingHorizontal: 14, + paddingVertical: 11, + backgroundColor: colors.bg, + borderBottomWidth: StyleSheet.hairlineWidth, + borderBottomColor: colors.border, + }, + dmAvatar: { + width: 42, + height: 42, + borderRadius: 21, + backgroundColor: colors.surfaceElevated, + alignItems: 'center', + justifyContent: 'center', + overflow: 'hidden', + marginRight: 10, + }, + dmAvatarImg: { width: 42, height: 42 }, + dmAvatarInitials: { + fontSize: 13, + fontFamily: 'Nunito_700Bold', + color: colors.textMuted, + }, + dmInfo: { flex: 1, minWidth: 0 }, + dmHeaderRow: { + flexDirection: 'row', + alignItems: 'center', + justifyContent: 'space-between', + }, + dmName: { + fontSize: 14, + fontFamily: 'Nunito_700Bold', + color: colors.text, + flexShrink: 1, + marginRight: 6, + }, + dmTime: { fontSize: 11, fontFamily: 'Nunito_600SemiBold' }, + dmBottomRow: { + flexDirection: 'row', + alignItems: 'center', + justifyContent: 'space-between', + marginTop: 2, + }, + dmLast: { fontSize: 12, flex: 1 }, + unreadBadge: { + minWidth: 20, + height: 20, + paddingHorizontal: 6, + borderRadius: 10, + backgroundColor: '#007AFF', + alignItems: 'center', + justifyContent: 'center', + marginLeft: 8, + }, + unreadBadgeText: { + fontSize: 10, + fontFamily: 'Nunito_700Bold', + color: '#fff', + }, + }); +} diff --git a/apps/rebreak-native/app/(app)/coach.tsx b/apps/rebreak-native/app/(app)/coach.tsx index d2b9bdb..88ca1a3 100644 --- a/apps/rebreak-native/app/(app)/coach.tsx +++ b/apps/rebreak-native/app/(app)/coach.tsx @@ -1,6 +1,7 @@ import { useCallback } from 'react'; import { View } from 'react-native'; import { useRouter, useFocusEffect } from 'expo-router'; +import { useColors } from '../../lib/theme'; /** * Placeholder-Screen für den Coach-Tab. @@ -11,6 +12,7 @@ import { useRouter, useFocusEffect } from 'expo-router'; */ export default function CoachTabRedirect() { const router = useRouter(); + const colors = useColors(); useFocusEffect( useCallback(() => { @@ -20,5 +22,5 @@ export default function CoachTabRedirect() { }, [router]), ); - return ; + return ; } diff --git a/apps/rebreak-native/app/(app)/mail.tsx b/apps/rebreak-native/app/(app)/mail.tsx index 6b5ba24..89cf794 100644 --- a/apps/rebreak-native/app/(app)/mail.tsx +++ b/apps/rebreak-native/app/(app)/mail.tsx @@ -20,10 +20,12 @@ import { SuccessAlert } from '../../components/SuccessAlert'; import { useMailStatus } from '../../hooks/useMailStatus'; import { useMailDisconnect } from '../../hooks/useMailDisconnect'; import { useUserPlan } from '../../hooks/useUserPlan'; +import { useColors } from '../../lib/theme'; export default function MailScreen() { const { t } = useTranslation(); const tabBarHeight = useBottomTabBarHeight(); + const colors = useColors(); const { plan } = useUserPlan(); @@ -72,7 +74,7 @@ export default function MailScreen() { if (loading) { return ( - + @@ -82,7 +84,7 @@ export default function MailScreen() { } return ( - + @@ -147,7 +149,7 @@ export default function MailScreen() { disabled={limitReached} android_ripple={{ color: '#0066cc' }} style={{ - backgroundColor: limitReached ? '#e5e5e5' : '#007AFF', + backgroundColor: limitReached ? colors.surfaceElevated : '#007AFF', borderRadius: 12, opacity: limitReached ? 0.7 : 1, shadowColor: '#007AFF', @@ -169,14 +171,14 @@ export default function MailScreen() { {t('mail.add_account')} diff --git a/apps/rebreak-native/app/(app)/notifications.tsx b/apps/rebreak-native/app/(app)/notifications.tsx index 4c2aa8c..8ceae8a 100644 --- a/apps/rebreak-native/app/(app)/notifications.tsx +++ b/apps/rebreak-native/app/(app)/notifications.tsx @@ -7,11 +7,12 @@ import { HeroShieldCheck } from '../../components/HeroShieldCheck'; import { useTranslation } from 'react-i18next'; import { EmptyState } from '../../components/EmptyState'; import { useNotificationStore, type AppNotification } from '../../stores/notifications'; -import { colors } from '../../lib/theme'; +import { useColors } from '../../lib/theme'; export default function NotificationsScreen() { const router = useRouter(); const { t } = useTranslation(); + const colors = useColors(); const items = useNotificationStore((s) => s.items); const loaded = useNotificationStore((s) => s.loaded); const load = useNotificationStore((s) => s.load); @@ -28,17 +29,16 @@ export default function NotificationsScreen() { }, []); return ( - - + + router.back()} - className="w-9 h-9 rounded-full bg-neutral-100 border border-neutral-200 items-center justify-center" + style={{ width: 36, height: 36, borderRadius: 18, backgroundColor: colors.surfaceElevated, borderWidth: 1, borderColor: colors.border, alignItems: 'center', justifyContent: 'center' }} > - + {t('notifications.title')} @@ -59,7 +59,7 @@ export default function NotificationsScreen() { } renderItem={({ item }) => ( @@ -88,6 +88,7 @@ function NotificationRow({ onPress: () => void; onDelete: () => void; }) { + const colors = useColors(); const isUnread = !notif.readAt; return ( {/* Pure-Icon — KEIN bg-Circle (User-Wunsch: kein extra Rand). */} @@ -117,7 +118,7 @@ function NotificationRow({ {notif.actorName} @@ -127,7 +128,7 @@ function NotificationRow({ style={{ fontSize: 12, fontFamily: 'Nunito_400Regular', - color: '#525252', + color: colors.textMuted, marginTop: 2, }} numberOfLines={2} diff --git a/apps/rebreak-native/app/auth/callback.tsx b/apps/rebreak-native/app/auth/callback.tsx index 5f2caf0..911f11d 100644 --- a/apps/rebreak-native/app/auth/callback.tsx +++ b/apps/rebreak-native/app/auth/callback.tsx @@ -15,10 +15,11 @@ import { useEffect } from 'react'; import { View, ActivityIndicator } from 'react-native'; import { useRouter, useLocalSearchParams } from 'expo-router'; import { supabase } from '../../lib/supabase'; -import { colors } from '../../lib/theme'; +import { useColors } from '../../lib/theme'; export default function AuthCallback() { const router = useRouter(); + const colors = useColors(); const params = useLocalSearchParams<{ access_token?: string; refresh_token?: string }>(); useEffect(() => { @@ -50,7 +51,7 @@ export default function AuthCallback() { }, []); return ( - + ); diff --git a/apps/rebreak-native/app/debug.tsx b/apps/rebreak-native/app/debug.tsx index 0fa313b..bd86267 100644 --- a/apps/rebreak-native/app/debug.tsx +++ b/apps/rebreak-native/app/debug.tsx @@ -3,10 +3,11 @@ import { View, Text, ScrollView, Pressable } from 'react-native'; import { SafeAreaView } from 'react-native-safe-area-context'; import { useRouter } from 'expo-router'; import { Ionicons } from '@expo/vector-icons'; -import { colors } from '../lib/theme'; +import { useColors } from '../lib/theme'; export default function DebugScreen() { const router = useRouter(); + const colors = useColors(); useEffect(() => { if (!__DEV__) { @@ -15,11 +16,11 @@ export default function DebugScreen() { }, [router]); if (!__DEV__) { - return ; + return ; } return ( - + - + Debug @@ -119,10 +120,11 @@ function DebugStub({ subtitle: string; icon: React.ComponentProps['name']; }) { + const colors = useColors(); return ( - + - {title} + {title} (null); const [myUserId, setMyUserId] = useState(undefined); @@ -234,7 +236,7 @@ export default function DmScreen() { {/* Header */} router.back()} hitSlop={8}> - + @@ -260,7 +262,7 @@ export default function DmScreen() { > {isLoading && messages.length === 0 ? ( - + ) : messages.length === 0 ? ( @@ -302,64 +304,66 @@ export default function DmScreen() { ); } -const styles = StyleSheet.create({ - container: { flex: 1, backgroundColor: '#fafafa' }, - header: { - flexDirection: 'row', - alignItems: 'center', - justifyContent: 'space-between', - paddingHorizontal: 12, - paddingVertical: 10, - backgroundColor: '#fff', - borderBottomWidth: StyleSheet.hairlineWidth, - borderBottomColor: '#e5e5e5', - }, - backBtn: { - width: 36, - height: 36, - borderRadius: 12, - backgroundColor: '#f5f5f5', - alignItems: 'center', - justifyContent: 'center', - }, - headerCenter: { - flex: 1, - flexDirection: 'row', - alignItems: 'center', - justifyContent: 'center', - marginHorizontal: 8, - }, - headerAvatar: { - width: 32, - height: 32, - borderRadius: 16, - backgroundColor: '#e5e5e5', - alignItems: 'center', - justifyContent: 'center', - overflow: 'hidden', - marginRight: 8, - }, - headerAvatarImg: { width: 32, height: 32 }, - headerAvatarInitials: { - fontSize: 11, - fontFamily: 'Nunito_700Bold', - color: '#737373', - }, - headerName: { - fontSize: 15, - fontFamily: 'Nunito_700Bold', - color: '#171717', - flexShrink: 1, - }, - loadingBox: { - flex: 1, - alignItems: 'center', - justifyContent: 'center', - }, - emptyText: { - fontSize: 13, - fontFamily: 'Nunito_600SemiBold', - color: '#a3a3a3', - marginTop: 12, - }, -}); +function makeStyles(colors: ReturnType) { + return StyleSheet.create({ + container: { flex: 1, backgroundColor: colors.bg }, + header: { + flexDirection: 'row', + alignItems: 'center', + justifyContent: 'space-between', + paddingHorizontal: 12, + paddingVertical: 10, + backgroundColor: colors.bg, + borderBottomWidth: StyleSheet.hairlineWidth, + borderBottomColor: colors.border, + }, + backBtn: { + width: 36, + height: 36, + borderRadius: 12, + backgroundColor: colors.surfaceElevated, + alignItems: 'center', + justifyContent: 'center', + }, + headerCenter: { + flex: 1, + flexDirection: 'row', + alignItems: 'center', + justifyContent: 'center', + marginHorizontal: 8, + }, + headerAvatar: { + width: 32, + height: 32, + borderRadius: 16, + backgroundColor: colors.surfaceElevated, + alignItems: 'center', + justifyContent: 'center', + overflow: 'hidden', + marginRight: 8, + }, + headerAvatarImg: { width: 32, height: 32 }, + headerAvatarInitials: { + fontSize: 11, + fontFamily: 'Nunito_700Bold', + color: colors.textMuted, + }, + headerName: { + fontSize: 15, + fontFamily: 'Nunito_700Bold', + color: colors.text, + flexShrink: 1, + }, + loadingBox: { + flex: 1, + alignItems: 'center', + justifyContent: 'center', + }, + emptyText: { + fontSize: 13, + fontFamily: 'Nunito_600SemiBold', + color: colors.textMuted, + marginTop: 12, + }, + }); +} diff --git a/apps/rebreak-native/app/games.tsx b/apps/rebreak-native/app/games.tsx index ba985af..8759025 100644 --- a/apps/rebreak-native/app/games.tsx +++ b/apps/rebreak-native/app/games.tsx @@ -13,7 +13,7 @@ import { TetrisGame, } from '../components/urge/UrgeGames'; import { GameCard } from '../components/games/GameCard'; -import { colors } from '../lib/theme'; +import { useColors } from '../lib/theme'; import { apiFetch } from '../lib/api'; type GameStat = { avgStars: number; count: number }; @@ -31,6 +31,7 @@ type LastScore = { game: GameType; score: number } | null; export default function GamesScreen() { const router = useRouter(); const { t } = useTranslation(); + const colors = useColors(); const [active, setActive] = useState(null); const [lastScore, setLastScore] = useState(null); const [gameStats, setGameStats] = useState(EMPTY_STATS); @@ -70,7 +71,7 @@ export default function GamesScreen() { if (active) { return ( - + - + {t(GAME_META.find((g) => g.id === active)!.titleKey)} @@ -127,7 +128,7 @@ export default function GamesScreen() { } return ( - + - + {t('games.title')} @@ -169,7 +170,7 @@ export default function GamesScreen() { (); const [imageFailed, setImageFailed] = useState(false); const [isFollowing, setIsFollowing] = useState(DUMMY_FOREIGN.isFollowing); @@ -99,13 +101,13 @@ export default function ForeignProfileScreen() { const planStyle = planColors[profile.plan]; return ( - + {showImage ? ( @@ -215,9 +217,9 @@ export default function ForeignProfileScreen() { (me?.avatar ?? null); const [photoUri, setPhotoUri] = useState(null); diff --git a/apps/rebreak-native/app/room.tsx b/apps/rebreak-native/app/room.tsx index eac1f7a..a092e31 100644 --- a/apps/rebreak-native/app/room.tsx +++ b/apps/rebreak-native/app/room.tsx @@ -27,7 +27,7 @@ import { supabase } from '../lib/supabase'; import { ChatBubble, type ChatMsg } from '../components/chat/ChatBubble'; import { ChatInput, type SendPayload } from '../components/chat/ChatInput'; import { useRoomRealtime } from '../hooks/useChatRealtime'; -import { colors } from '../lib/theme'; +import { useColors } from '../lib/theme'; const GROUP_GAP_MS = 5 * 60 * 1000; @@ -64,6 +64,8 @@ export default function RoomScreen() { const { t } = useTranslation(); const router = useRouter(); const insets = useSafeAreaInsets(); + const colors = useColors(); + const styles = makeStyles(colors); const queryClient = useQueryClient(); const flatRef = useRef(null); const [myUserId, setMyUserId] = useState(); @@ -298,7 +300,7 @@ export default function RoomScreen() { {/* Header */} router.back()} hitSlop={8}> - + @@ -320,7 +322,7 @@ export default function RoomScreen() { setSettingsOpen(true)} hitSlop={8}> - + @@ -430,6 +432,8 @@ function RoomSettingsModal({ roomId: string; }) { const { t } = useTranslation(); + const colors = useColors(); + const modal = makeModalStyles(colors); const [pendingRequests, setPendingRequests] = useState([]); const [loadingReqs, setLoadingReqs] = useState(false); @@ -637,221 +641,225 @@ function RoomSettingsModal({ ); } -const styles = StyleSheet.create({ - container: { flex: 1, backgroundColor: '#fafafa' }, - header: { - flexDirection: 'row', - alignItems: 'center', - paddingHorizontal: 12, - paddingVertical: 10, - backgroundColor: '#fff', - borderBottomWidth: StyleSheet.hairlineWidth, - borderBottomColor: '#e5e5e5', - }, - iconBtn: { - width: 36, - height: 36, - borderRadius: 12, - backgroundColor: '#f5f5f5', - alignItems: 'center', - justifyContent: 'center', - }, - headerCenter: { - flex: 1, - flexDirection: 'row', - alignItems: 'center', - marginHorizontal: 8, - }, - headerAvatar: { - width: 36, - height: 36, - borderRadius: 18, - backgroundColor: '#e5e5e5', - alignItems: 'center', - justifyContent: 'center', - overflow: 'hidden', - marginRight: 8, - }, - headerAvatarImg: { width: 36, height: 36 }, - headerAvatarInitials: { - fontSize: 12, - fontFamily: 'Nunito_700Bold', - color: '#737373', - }, - headerName: { - fontSize: 15, - fontFamily: 'Nunito_700Bold', - color: '#171717', - }, - headerSub: { - fontSize: 11, - fontFamily: 'Nunito_500Medium', - color: '#737373', - marginTop: 1, - }, - loadingBox: { flex: 1, alignItems: 'center', justifyContent: 'center' }, - joinBox: { - flex: 1, - alignItems: 'center', - justifyContent: 'center', - paddingHorizontal: 32, - }, - joinTitle: { - fontSize: 20, - fontFamily: 'Nunito_700Bold', - color: '#171717', - marginTop: 14, - }, - joinDesc: { - fontSize: 13, - fontFamily: 'Nunito_500Medium', - color: '#737373', - marginTop: 6, - textAlign: 'center', - }, - joinHint: { - fontSize: 12, - fontFamily: 'Nunito_500Medium', - color: '#a3a3a3', - marginTop: 18, - textAlign: 'center', - }, - joinBtn: { - marginTop: 16, - backgroundColor: '#007AFF', - paddingHorizontal: 32, - paddingVertical: 12, - borderRadius: 12, - minWidth: 140, - alignItems: 'center', - }, - joinBtnText: { - color: '#fff', - fontSize: 14, - fontFamily: 'Nunito_700Bold', - }, - pendingBadge: { - flexDirection: 'row', - alignItems: 'center', - backgroundColor: '#fef3c7', - paddingHorizontal: 14, - paddingVertical: 8, - borderRadius: 20, - marginTop: 16, - }, - pendingText: { - color: '#92400e', - fontSize: 12, - fontFamily: 'Nunito_700Bold', - marginLeft: 6, - }, -}); +function makeStyles(colors: ReturnType) { + return StyleSheet.create({ + container: { flex: 1, backgroundColor: colors.bg }, + header: { + flexDirection: 'row', + alignItems: 'center', + paddingHorizontal: 12, + paddingVertical: 10, + backgroundColor: colors.bg, + borderBottomWidth: StyleSheet.hairlineWidth, + borderBottomColor: colors.border, + }, + iconBtn: { + width: 36, + height: 36, + borderRadius: 12, + backgroundColor: colors.surfaceElevated, + alignItems: 'center', + justifyContent: 'center', + }, + headerCenter: { + flex: 1, + flexDirection: 'row', + alignItems: 'center', + marginHorizontal: 8, + }, + headerAvatar: { + width: 36, + height: 36, + borderRadius: 18, + backgroundColor: colors.surfaceElevated, + alignItems: 'center', + justifyContent: 'center', + overflow: 'hidden', + marginRight: 8, + }, + headerAvatarImg: { width: 36, height: 36 }, + headerAvatarInitials: { + fontSize: 12, + fontFamily: 'Nunito_700Bold', + color: colors.textMuted, + }, + headerName: { + fontSize: 15, + fontFamily: 'Nunito_700Bold', + color: colors.text, + }, + headerSub: { + fontSize: 11, + fontFamily: 'Nunito_500Medium', + color: colors.textMuted, + marginTop: 1, + }, + loadingBox: { flex: 1, alignItems: 'center', justifyContent: 'center' }, + joinBox: { + flex: 1, + alignItems: 'center', + justifyContent: 'center', + paddingHorizontal: 32, + }, + joinTitle: { + fontSize: 20, + fontFamily: 'Nunito_700Bold', + color: colors.text, + marginTop: 14, + }, + joinDesc: { + fontSize: 13, + fontFamily: 'Nunito_500Medium', + color: colors.textMuted, + marginTop: 6, + textAlign: 'center', + }, + joinHint: { + fontSize: 12, + fontFamily: 'Nunito_500Medium', + color: colors.textMuted, + marginTop: 18, + textAlign: 'center', + }, + joinBtn: { + marginTop: 16, + backgroundColor: '#007AFF', + paddingHorizontal: 32, + paddingVertical: 12, + borderRadius: 12, + minWidth: 140, + alignItems: 'center', + }, + joinBtnText: { + color: '#fff', + fontSize: 14, + fontFamily: 'Nunito_700Bold', + }, + pendingBadge: { + flexDirection: 'row', + alignItems: 'center', + backgroundColor: '#fef3c7', + paddingHorizontal: 14, + paddingVertical: 8, + borderRadius: 20, + marginTop: 16, + }, + pendingText: { + color: '#92400e', + fontSize: 12, + fontFamily: 'Nunito_700Bold', + marginLeft: 6, + }, + }); +} -const modal = StyleSheet.create({ - container: { flex: 1, backgroundColor: '#fafafa' }, - header: { - flexDirection: 'row', - alignItems: 'center', - justifyContent: 'space-between', - paddingHorizontal: 16, - paddingVertical: 12, - backgroundColor: '#fff', - borderBottomWidth: StyleSheet.hairlineWidth, - borderBottomColor: '#e5e5e5', - }, - title: { fontSize: 16, fontFamily: 'Nunito_700Bold', color: '#171717' }, - section: { - backgroundColor: '#fff', - borderRadius: 12, - padding: 14, - marginBottom: 12, - }, - sectionTitle: { - fontSize: 12, - fontFamily: 'Nunito_700Bold', - color: '#737373', - textTransform: 'uppercase', - marginBottom: 10, - letterSpacing: 0.5, - }, - avatarWrap: { alignSelf: 'center', marginBottom: 10 }, - avatar: { width: 80, height: 80, borderRadius: 40 }, - avatarPlaceholder: { - backgroundColor: '#e5e5e5', - alignItems: 'center', - justifyContent: 'center', - }, - avatarEdit: { - position: 'absolute', - right: -2, - bottom: -2, - width: 28, - height: 28, - borderRadius: 14, - backgroundColor: '#007AFF', - borderWidth: 3, - borderColor: '#fff', - alignItems: 'center', - justifyContent: 'center', - }, - roomName: { - fontSize: 17, - fontFamily: 'Nunito_700Bold', - color: '#171717', - textAlign: 'center', - }, - roomDesc: { - fontSize: 12, - fontFamily: 'Nunito_500Medium', - color: '#737373', - textAlign: 'center', - marginTop: 4, - }, - memberRow: { - flexDirection: 'row', - alignItems: 'center', - paddingVertical: 8, - borderBottomWidth: StyleSheet.hairlineWidth, - borderBottomColor: '#f5f5f5', - }, - memberAvatar: { - width: 32, - height: 32, - borderRadius: 16, - backgroundColor: '#e5e5e5', - alignItems: 'center', - justifyContent: 'center', - overflow: 'hidden', - marginRight: 10, - }, - memberAvatarImg: { width: 32, height: 32 }, - memberInitials: { - fontSize: 11, - fontFamily: 'Nunito_700Bold', - color: '#737373', - }, - memberName: { fontSize: 13, fontFamily: 'Nunito_600SemiBold', color: '#171717' }, - memberRole: { fontSize: 11, color: '#a3a3a3', marginTop: 1, textTransform: 'capitalize' }, - actionBtn: { - paddingHorizontal: 10, - paddingVertical: 5, - borderRadius: 6, - }, - actionText: { fontSize: 11, fontFamily: 'Nunito_700Bold' }, - emptyText: { fontSize: 12, color: '#a3a3a3' }, - leaveBtn: { - flexDirection: 'row', - alignItems: 'center', - justifyContent: 'center', - backgroundColor: '#fee2e2', - paddingVertical: 12, - borderRadius: 10, - marginTop: 8, - }, - leaveText: { - color: '#991b1b', - fontSize: 13, - fontFamily: 'Nunito_700Bold', - marginLeft: 6, - }, -}); +function makeModalStyles(colors: ReturnType) { + return StyleSheet.create({ + container: { flex: 1, backgroundColor: colors.bg }, + header: { + flexDirection: 'row', + alignItems: 'center', + justifyContent: 'space-between', + paddingHorizontal: 16, + paddingVertical: 12, + backgroundColor: colors.bg, + borderBottomWidth: StyleSheet.hairlineWidth, + borderBottomColor: colors.border, + }, + title: { fontSize: 16, fontFamily: 'Nunito_700Bold', color: colors.text }, + section: { + backgroundColor: colors.surface, + borderRadius: 12, + padding: 14, + marginBottom: 12, + }, + sectionTitle: { + fontSize: 12, + fontFamily: 'Nunito_700Bold', + color: colors.textMuted, + textTransform: 'uppercase', + marginBottom: 10, + letterSpacing: 0.5, + }, + avatarWrap: { alignSelf: 'center', marginBottom: 10 }, + avatar: { width: 80, height: 80, borderRadius: 40 }, + avatarPlaceholder: { + backgroundColor: colors.surfaceElevated, + alignItems: 'center', + justifyContent: 'center', + }, + avatarEdit: { + position: 'absolute', + right: -2, + bottom: -2, + width: 28, + height: 28, + borderRadius: 14, + backgroundColor: '#007AFF', + borderWidth: 3, + borderColor: colors.bg, + alignItems: 'center', + justifyContent: 'center', + }, + roomName: { + fontSize: 17, + fontFamily: 'Nunito_700Bold', + color: colors.text, + textAlign: 'center', + }, + roomDesc: { + fontSize: 12, + fontFamily: 'Nunito_500Medium', + color: colors.textMuted, + textAlign: 'center', + marginTop: 4, + }, + memberRow: { + flexDirection: 'row', + alignItems: 'center', + paddingVertical: 8, + borderBottomWidth: StyleSheet.hairlineWidth, + borderBottomColor: colors.border, + }, + memberAvatar: { + width: 32, + height: 32, + borderRadius: 16, + backgroundColor: colors.surfaceElevated, + alignItems: 'center', + justifyContent: 'center', + overflow: 'hidden', + marginRight: 10, + }, + memberAvatarImg: { width: 32, height: 32 }, + memberInitials: { + fontSize: 11, + fontFamily: 'Nunito_700Bold', + color: colors.textMuted, + }, + memberName: { fontSize: 13, fontFamily: 'Nunito_600SemiBold', color: colors.text }, + memberRole: { fontSize: 11, color: colors.textMuted, marginTop: 1, textTransform: 'capitalize' }, + actionBtn: { + paddingHorizontal: 10, + paddingVertical: 5, + borderRadius: 6, + }, + actionText: { fontSize: 11, fontFamily: 'Nunito_700Bold' }, + emptyText: { fontSize: 12, color: colors.textMuted }, + leaveBtn: { + flexDirection: 'row', + alignItems: 'center', + justifyContent: 'center', + backgroundColor: '#fee2e2', + paddingVertical: 12, + borderRadius: 10, + marginTop: 8, + }, + leaveText: { + color: '#991b1b', + fontSize: 13, + fontFamily: 'Nunito_700Bold', + marginLeft: 6, + }, + }); +} diff --git a/apps/rebreak-native/app/urge.tsx b/apps/rebreak-native/app/urge.tsx index 4e474f5..2cd21db 100644 --- a/apps/rebreak-native/app/urge.tsx +++ b/apps/rebreak-native/app/urge.tsx @@ -15,7 +15,7 @@ import { useTranslation } from 'react-i18next'; import { RiveAvatar } from '../components/RiveAvatar'; import { apiFetch } from '../lib/api'; import { supabase } from '../lib/supabase'; -import { colors } from '../lib/theme'; +import { useColors } from '../lib/theme'; import { type GameType, GAME_META, MemoryGame, TicTacToeGame, SnakeGame, TetrisGame, } from '../components/urge/UrgeGames'; @@ -41,6 +41,8 @@ export default function SOSScreen() { const { t, i18n } = useTranslation(); const router = useRouter(); const insets = useSafeAreaInsets(); + const colors = useColors(); + const st = makeStyles(colors); const flatRef = useRef(null); const [messages, setMessages] = useState([]); @@ -1089,7 +1091,7 @@ export default function SOSScreen() { {/* Header */} - + @@ -1286,39 +1288,41 @@ export default function SOSScreen() { ); } -const st = StyleSheet.create({ - container: { flex: 1, backgroundColor: '#ffffff' }, - topBar: { position: 'absolute', left: 0, right: 0, zIndex: 10, flexDirection: 'row', alignItems: 'flex-start', justifyContent: 'space-between', paddingHorizontal: 12 }, - topBarBackdrop: { position: 'absolute', top: 0, left: 0, right: 0, zIndex: 9, backgroundColor: '#ffffff' }, - actionBtn: { width: 40, height: 40, borderRadius: 20, backgroundColor: 'rgba(255,255,255,0.92)', alignItems: 'center', justifyContent: 'center', shadowColor: '#000', shadowOffset: { width: 0, height: 2 }, shadowOpacity: 0.08, shadowRadius: 6, elevation: 4 }, - avatarCenter: { flex: 1, alignItems: 'center', gap: 4 }, - avatarMeta: { alignItems: 'center', gap: 2 }, - avatarName: { fontSize: 14, fontFamily: 'Nunito_700Bold', color: '#0a0a0a' }, - speakingRow: { flexDirection: 'row', alignItems: 'center', gap: 6 }, - stopBtn: { width: 18, height: 18, borderRadius: 9, backgroundColor: '#f5f5f5', alignItems: 'center', justifyContent: 'center' }, - listContent: { paddingHorizontal: 12, paddingBottom: 4 }, - scrollDownBtn: { position: 'absolute', bottom: 8, right: 16, width: 36, height: 36, borderRadius: 18, backgroundColor: '#374151', alignItems: 'center', justifyContent: 'center', shadowColor: '#000', shadowOffset: { width: 0, height: 2 }, shadowOpacity: 0.15, shadowRadius: 4, elevation: 4 }, - chip: { - borderRadius: 14, - borderWidth: 1.5, - borderColor: '#9ca3af', // sichtbarer Ring (medium-grau gegen weiß) - backgroundColor: '#ffffff', - paddingHorizontal: 16, - paddingVertical: 11, - shadowColor: '#000', - shadowOffset: { width: 0, height: 2 }, - shadowOpacity: 0.12, - shadowRadius: 6, - elevation: 3, - }, - chipPressed: { - backgroundColor: '#f3f4f6', - borderColor: '#6b7280', // dunkler beim Press → spürbares Feedback - transform: [{ scale: 0.97 }], - shadowOpacity: 0.05, - }, - chipText: { fontFamily: 'Nunito_600SemiBold', fontSize: 14, color: '#334155' }, - inputBar: { flexDirection: 'row', alignItems: 'flex-end', paddingHorizontal: 12, paddingTop: 8, borderTopWidth: 1, borderTopColor: '#f3f4f6', backgroundColor: '#fff', gap: 8 }, - textInput: { flex: 1, minHeight: 40, maxHeight: 120, backgroundColor: '#f3f4f6', borderRadius: 20, paddingHorizontal: 14, paddingVertical: 10, fontSize: 15, fontFamily: 'Nunito_400Regular', color: '#111827' }, - sendBtn: { width: 38, height: 38, borderRadius: 19, backgroundColor: '#007AFF', alignItems: 'center', justifyContent: 'center' }, -}); +function makeStyles(colors: ReturnType) { + return StyleSheet.create({ + container: { flex: 1, backgroundColor: colors.bg }, + topBar: { position: 'absolute', left: 0, right: 0, zIndex: 10, flexDirection: 'row', alignItems: 'flex-start', justifyContent: 'space-between', paddingHorizontal: 12 }, + topBarBackdrop: { position: 'absolute', top: 0, left: 0, right: 0, zIndex: 9, backgroundColor: colors.bg }, + actionBtn: { width: 40, height: 40, borderRadius: 20, backgroundColor: colors.surface, alignItems: 'center', justifyContent: 'center', shadowColor: '#000', shadowOffset: { width: 0, height: 2 }, shadowOpacity: 0.08, shadowRadius: 6, elevation: 4 }, + avatarCenter: { flex: 1, alignItems: 'center', gap: 4 }, + avatarMeta: { alignItems: 'center', gap: 2 }, + avatarName: { fontSize: 14, fontFamily: 'Nunito_700Bold', color: colors.text }, + speakingRow: { flexDirection: 'row', alignItems: 'center', gap: 6 }, + stopBtn: { width: 18, height: 18, borderRadius: 9, backgroundColor: colors.surfaceElevated, alignItems: 'center', justifyContent: 'center' }, + listContent: { paddingHorizontal: 12, paddingBottom: 4 }, + scrollDownBtn: { position: 'absolute', bottom: 8, right: 16, width: 36, height: 36, borderRadius: 18, backgroundColor: '#374151', alignItems: 'center', justifyContent: 'center', shadowColor: '#000', shadowOffset: { width: 0, height: 2 }, shadowOpacity: 0.15, shadowRadius: 4, elevation: 4 }, + chip: { + borderRadius: 14, + borderWidth: 1.5, + borderColor: colors.border, + backgroundColor: colors.bg, + paddingHorizontal: 16, + paddingVertical: 11, + shadowColor: '#000', + shadowOffset: { width: 0, height: 2 }, + shadowOpacity: 0.12, + shadowRadius: 6, + elevation: 3, + }, + chipPressed: { + backgroundColor: colors.surfaceElevated, + borderColor: colors.textMuted, + transform: [{ scale: 0.97 }], + shadowOpacity: 0.05, + }, + chipText: { fontFamily: 'Nunito_600SemiBold', fontSize: 14, color: colors.textMuted }, + inputBar: { flexDirection: 'row', alignItems: 'flex-end', paddingHorizontal: 12, paddingTop: 8, borderTopWidth: 1, borderTopColor: colors.border, backgroundColor: colors.bg, gap: 8 }, + textInput: { flex: 1, minHeight: 40, maxHeight: 120, backgroundColor: colors.surfaceElevated, borderRadius: 20, paddingHorizontal: 14, paddingVertical: 10, fontSize: 15, fontFamily: 'Nunito_400Regular', color: colors.text }, + sendBtn: { width: 38, height: 38, borderRadius: 19, backgroundColor: '#007AFF', alignItems: 'center', justifyContent: 'center' }, + }); +} diff --git a/apps/rebreak-native/components/ComposeCard.tsx b/apps/rebreak-native/components/ComposeCard.tsx index 17d440c..afa1035 100644 --- a/apps/rebreak-native/components/ComposeCard.tsx +++ b/apps/rebreak-native/components/ComposeCard.tsx @@ -17,7 +17,7 @@ import * as ImagePicker from 'expo-image-picker'; import { apiFetch } from '../lib/api'; import { resolveAvatar } from '../lib/resolveAvatar'; import { useAuthStore } from '../stores/auth'; -import { colors } from '../lib/theme'; +import { useColors } from '../lib/theme'; type Props = { onPosted?: () => void; @@ -25,6 +25,7 @@ type Props = { export function ComposeCard({ onPosted }: Props) { const { t } = useTranslation(); + const colors = useColors(); const { user } = useAuthStore(); const queryClient = useQueryClient(); const inputRef = useRef(null); @@ -101,7 +102,7 @@ export function ComposeCard({ onPosted }: Props) { const showActions = focused || content.length > 0; return ( - + setFocused(true)} placeholder={t('community.compose_placeholder')} - placeholderTextColor="#a3a3a3" + placeholderTextColor={colors.textMuted} multiline - className="text-sm text-neutral-900 leading-5 min-h-[40px]" - style={{ textAlignVertical: 'top', fontFamily: 'Nunito_400Regular' }} + className="text-sm leading-5 min-h-[40px]" + style={{ textAlignVertical: 'top', fontFamily: 'Nunito_400Regular', color: colors.text }} /> {imageUri && ( diff --git a/apps/rebreak-native/components/DeviceLimitReachedSheet.tsx b/apps/rebreak-native/components/DeviceLimitReachedSheet.tsx index 2ad7d89..1d57e12 100644 --- a/apps/rebreak-native/components/DeviceLimitReachedSheet.tsx +++ b/apps/rebreak-native/components/DeviceLimitReachedSheet.tsx @@ -3,7 +3,7 @@ import { useEffect, useRef, useState } from 'react'; import { TrueSheet, type SheetDetent } from '@lodev09/react-native-true-sheet'; import { Ionicons } from '@expo/vector-icons'; import { useTranslation } from 'react-i18next'; -import { colors } from '../lib/theme'; +import { useColors } from '../lib/theme'; import { apiFetch } from '../lib/api'; import { useDeviceLimitStore, type DeviceLimitDevice } from '../stores/deviceLimit'; @@ -40,6 +40,7 @@ function DeviceLimitRow({ onRemove: (id: string) => void; }) { const { t } = useTranslation(); + const colors = useColors(); return ( (null); const { visible, devices, max, plan, hide, removeDevice } = useDeviceLimitStore(); const [removingId, setRemovingId] = useState(null); @@ -195,7 +197,7 @@ export function DeviceLimitReachedSheet() { s.items); const loaded = useNotificationStore((s) => s.loaded); @@ -71,7 +73,7 @@ export function NotificationsDropdown({ visible, onClose, topOffset }: Props) { position: 'absolute', top: topOffset + 6, right: 12, - backgroundColor: '#ffffff', + backgroundColor: colors.bg, borderRadius: 18, shadowColor: '#000', shadowOffset: { width: 0, height: 8 }, @@ -93,11 +95,11 @@ export function NotificationsDropdown({ visible, onClose, topOffset }: Props) { paddingHorizontal: 16, paddingVertical: 12, borderBottomWidth: 1, - borderBottomColor: '#f0f0f0', + borderBottomColor: colors.border, }} > {t('notifications.title')} @@ -114,13 +116,13 @@ export function NotificationsDropdown({ visible, onClose, topOffset }: Props) { {items.length === 0 ? ( - + {t('notifications.empty_title')} @@ -130,7 +132,7 @@ export function NotificationsDropdown({ visible, onClose, topOffset }: Props) { marginTop: 2, fontSize: 11, fontFamily: 'Nunito_400Regular', - color: '#a3a3a3', + color: colors.textMuted, textAlign: 'center', paddingHorizontal: 20, }} @@ -221,6 +223,7 @@ function NotificationRow({ onPress: () => void; t: (k: string, opts?: any) => string; }) { + const colors = useColors(); const isUnread = !notif.readAt; const { icon, color, bg } = notifIcon(notif.type); const isSocial = @@ -249,8 +252,8 @@ function NotificationRow({ paddingHorizontal: 14, paddingVertical: 11, borderBottomWidth: 1, - borderBottomColor: '#f5f5f5', - backgroundColor: isUnread ? '#fff7ed' : '#ffffff', + borderBottomColor: colors.border, + backgroundColor: isUnread ? colors.surface : colors.bg, }} > {/* Avatar-Logik: @@ -297,7 +300,7 @@ function NotificationRow({ style={{ fontSize: 12, fontFamily: 'Nunito_600SemiBold', - color: '#0a0a0a', + color: colors.text, lineHeight: 16, }} numberOfLines={2} @@ -308,7 +311,7 @@ function NotificationRow({ style={{ fontSize: 10, fontFamily: 'Nunito_400Regular', - color: '#a3a3a3', + color: colors.textMuted, marginTop: 2, }} > diff --git a/apps/rebreak-native/components/OptionsBottomSheet.tsx b/apps/rebreak-native/components/OptionsBottomSheet.tsx index 5dc3750..7586724 100644 --- a/apps/rebreak-native/components/OptionsBottomSheet.tsx +++ b/apps/rebreak-native/components/OptionsBottomSheet.tsx @@ -22,7 +22,7 @@ import { Easing, } from 'react-native'; import { useSafeAreaInsets } from 'react-native-safe-area-context'; -import { colors } from '../lib/theme'; +import { useColors } from '../lib/theme'; type Option = { value: T; @@ -51,6 +51,7 @@ export function OptionsBottomSheet({ onClose, }: Props) { const insets = useSafeAreaInsets(); + const colors = useColors(); const translateY = useRef(new Animated.Value(400)).current; const backdropOpacity = useRef(new Animated.Value(0)).current; diff --git a/apps/rebreak-native/components/PostCard.tsx b/apps/rebreak-native/components/PostCard.tsx index 5f7cb84..e69e1a5 100644 --- a/apps/rebreak-native/components/PostCard.tsx +++ b/apps/rebreak-native/components/PostCard.tsx @@ -9,6 +9,7 @@ import { formatRelativeTime } from '../lib/formatTime'; import { useCommunityStore, type CommunityPost } from '../stores/community'; import { RiveAvatar } from './RiveAvatar'; import { HeroShieldCheck } from './HeroShieldCheck'; +import { useColors } from '../lib/theme'; type Props = { post: CommunityPost; @@ -17,6 +18,7 @@ type Props = { function PostCardImpl({ post, onCommentPress }: Props) { const { t } = useTranslation(); + const colors = useColors(); const queryClient = useQueryClient(); // Granular selectors — subscribing to the whole store would re-render every // PostCard whenever any user likes any post (optimisticLikes mutates). @@ -162,7 +164,7 @@ function PostCardImpl({ post, onCommentPress }: Props) { }, [isLiking, localLike, localCount, post.id, post.userLike, post.likesCount, applyOptimisticLike, clearOptimisticLike, revertOptimisticLike, queryClient, triggerHeartPop]); return ( - + {/* Repost header */} {post.repostOf && ( @@ -194,35 +196,35 @@ function PostCardImpl({ post, onCommentPress }: Props) { )} - + {authorLabel} {authorDescription !== undefined && ( - {authorDescription} + {authorDescription} )} - + {formatRelativeTime(post.createdAt)} {/* Content — hidden for domain_vote (replaced by poll below) */} {!!displayContent && post.category !== 'domain_vote' && ( - + {displayContent} )} {/* domain_approved: favicon + domain name + shield badge */} {post.category === 'domain_approved' && !!approvedDomain && ( - + - - + + {approvedDomain} - + {t('community.domain_added_to_blocklist')} diff --git a/apps/rebreak-native/components/PostCardSkeleton.tsx b/apps/rebreak-native/components/PostCardSkeleton.tsx index f5edf20..3473e7b 100644 --- a/apps/rebreak-native/components/PostCardSkeleton.tsx +++ b/apps/rebreak-native/components/PostCardSkeleton.tsx @@ -1,18 +1,20 @@ import { View } from 'react-native'; +import { useColors } from '../lib/theme'; export function PostCardSkeleton() { + const colors = useColors(); return ( - - - - - - + + + + + + - - - + + + ); } diff --git a/apps/rebreak-native/components/PostCommentsSheet.tsx b/apps/rebreak-native/components/PostCommentsSheet.tsx index 4a32b6b..88bb00d 100644 --- a/apps/rebreak-native/components/PostCommentsSheet.tsx +++ b/apps/rebreak-native/components/PostCommentsSheet.tsx @@ -19,7 +19,7 @@ import { useQuery, useQueryClient } from '@tanstack/react-query'; import { useTranslation } from 'react-i18next'; import { apiFetch } from '../lib/api'; import { formatRelativeTime } from '../lib/formatTime'; -import { colors } from '../lib/theme'; +import { useColors } from '../lib/theme'; import type { CommunityComment } from '../stores/community'; const EMOJIS = ['❤️', '🙌', '🔥', '👏', '😢', '😍', '😮', '😂']; @@ -33,6 +33,7 @@ type Props = { export function PostCommentsSheet({ postId, visible, onClose }: Props) { const { t } = useTranslation(); + const colors = useColors(); const insets = useSafeAreaInsets(); const queryClient = useQueryClient(); const inputRef = useRef(null); @@ -230,7 +231,7 @@ export function PostCommentsSheet({ postId, visible, onClose }: Props) { @@ -268,10 +269,10 @@ export function PostCommentsSheet({ postId, visible, onClose }: Props) { paddingTop: 6, paddingBottom: 12, borderBottomWidth: 1, - borderBottomColor: '#e5e5e5', + borderBottomColor: colors.border, }} > - + {t('community.comments_title')} @@ -323,7 +324,7 @@ export function PostCommentsSheet({ postId, visible, onClose }: Props) { paddingHorizontal: 16, paddingVertical: 8, borderTopWidth: 1, - borderTopColor: '#f5f5f5', + borderTopColor: colors.border, }} > {EMOJIS.map((e) => ( @@ -342,10 +343,10 @@ export function PostCommentsSheet({ postId, visible, onClose }: Props) { justifyContent: 'space-between', paddingHorizontal: 16, paddingVertical: 8, - backgroundColor: '#fafafa', + backgroundColor: colors.surface, }} > - + {t('community.reply_to')}{' '} @{replyTarget.nickname} @@ -366,7 +367,7 @@ export function PostCommentsSheet({ postId, visible, onClose }: Props) { // sonst Safe-Area paddingBottom: keyboardHeight > 0 ? 8 : Math.max(12, insets.bottom), borderTopWidth: 1, - borderTopColor: '#e5e5e5', + borderTopColor: colors.border, }} > { heartScale.setValue(1); @@ -447,26 +449,26 @@ function CommentRow({ comment, isReply = false, onReply, onLike }: CommentRowPro width: isReply ? 24 : 32, height: isReply ? 24 : 32, borderRadius: isReply ? 12 : 16, - backgroundColor: '#e5e5e5', + backgroundColor: colors.surfaceElevated, alignItems: 'center', justifyContent: 'center', marginTop: 2, }} > - + {(comment.authorNickname ?? 'AN').slice(0, 2).toUpperCase()} - + {comment.authorNickname ?? t('community.anonymous_label')} - + {formatRelativeTime(comment.createdAt)} {!isReply && onReply && ( - + {t('community.reply')} diff --git a/apps/rebreak-native/components/StreakBadge.tsx b/apps/rebreak-native/components/StreakBadge.tsx index cc03ee4..cd4f840 100644 --- a/apps/rebreak-native/components/StreakBadge.tsx +++ b/apps/rebreak-native/components/StreakBadge.tsx @@ -1,7 +1,7 @@ import { View, Text } from 'react-native'; import { Ionicons } from '@expo/vector-icons'; import { useTranslation } from 'react-i18next'; -import { colors } from '../lib/theme'; +import { useColors } from '../lib/theme'; type Props = { days: number; @@ -16,14 +16,18 @@ const sizeMap = { export function StreakBadge({ days, size = 'md' }: Props) { const { t } = useTranslation(); + const colors = useColors(); const s = sizeMap[size]; return ( - + - {days} + {days} - + {days === 1 ? t('streak.label_one') : t('streak.label_other')} {t('streak.label_suffix')} diff --git a/apps/rebreak-native/components/WheelPickerModal.tsx b/apps/rebreak-native/components/WheelPickerModal.tsx index 2ec6668..8b2ee81 100644 --- a/apps/rebreak-native/components/WheelPickerModal.tsx +++ b/apps/rebreak-native/components/WheelPickerModal.tsx @@ -14,7 +14,7 @@ import { useEffect, useState } from 'react'; import { Modal, View, Text, Pressable } from 'react-native'; import { Picker } from '@react-native-picker/picker'; -import { colors } from '../lib/theme'; +import { useColors } from '../lib/theme'; type Option = { value: T; label: string }; @@ -35,6 +35,7 @@ export function WheelPickerModal({ onSelect, onClose, }: Props) { + const colors = useColors(); // Tracks the wheel's current selection (separate from confirmed value). // Initialized from `value` prop on each open. const [tempValue, setTempValue] = useState(value); @@ -72,7 +73,7 @@ export function WheelPickerModal({ {}}> ({ paddingHorizontal: 16, paddingVertical: 12, borderBottomWidth: 1, - borderBottomColor: '#e5e5e5', + borderBottomColor: colors.border, }} > diff --git a/apps/rebreak-native/components/blocker/AddDomainSheet.tsx b/apps/rebreak-native/components/blocker/AddDomainSheet.tsx index 0b42d96..eacf8ef 100644 --- a/apps/rebreak-native/components/blocker/AddDomainSheet.tsx +++ b/apps/rebreak-native/components/blocker/AddDomainSheet.tsx @@ -21,6 +21,7 @@ import { normalizeDomain, type Tier, } from '../../hooks/useCustomDomains'; +import { useColors } from '../../lib/theme'; const SCREEN_HEIGHT = Dimensions.get('window').height; const SHEET_HEIGHT = SCREEN_HEIGHT * 0.65; // wie bei PostCommentsSheet — 65% der Screen-Höhe @@ -34,6 +35,7 @@ type Props = { export function AddDomainSheet({ visible, tier, onClose, onAdd }: Props) { const { t } = useTranslation(); + const colors = useColors(); const insets = useSafeAreaInsets(); const [input, setInput] = useState(''); const [confirmPermanent, setConfirmPermanent] = useState(false); @@ -122,7 +124,7 @@ export function AddDomainSheet({ visible, tier, onClose, onAdd }: Props) { right: 0, bottom: 0, height: SHEET_HEIGHT, - backgroundColor: '#fff', + backgroundColor: colors.bg, borderTopLeftRadius: 20, borderTopRightRadius: 20, transform: [{ translateY }], @@ -134,7 +136,7 @@ export function AddDomainSheet({ visible, tier, onClose, onAdd }: Props) { > {/* Drag-handle */} - + {/* Header */} @@ -147,15 +149,15 @@ export function AddDomainSheet({ visible, tier, onClose, onAdd }: Props) { paddingTop: 6, paddingBottom: 12, borderBottomWidth: 1, - borderBottomColor: '#f0f0f0', + borderBottomColor: colors.border, }} > - + {t('common.cancel')} - + {t('blocker.add_sheet_title')} @@ -168,7 +170,7 @@ export function AddDomainSheet({ visible, tier, onClose, onAdd }: Props) { style={{ fontSize: 12, fontFamily: 'Nunito_600SemiBold', - color: '#525252', + color: colors.textMuted, marginBottom: 6, }} > @@ -181,7 +183,7 @@ export function AddDomainSheet({ visible, tier, onClose, onAdd }: Props) { setError(null); }} placeholder={t('blocker.add_sheet_placeholder')} - placeholderTextColor="#a3a3a3" + placeholderTextColor={colors.textMuted} autoCapitalize="none" autoCorrect={false} autoFocus @@ -189,13 +191,13 @@ export function AddDomainSheet({ visible, tier, onClose, onAdd }: Props) { returnKeyType="done" onSubmitEditing={handleAdd} style={{ - backgroundColor: '#f5f5f5', + backgroundColor: colors.surfaceElevated, borderRadius: 12, paddingHorizontal: 14, paddingVertical: 12, fontSize: 15, fontFamily: 'Nunito_400Regular', - color: '#0a0a0a', + color: colors.text, }} /> {input && !valid && ( @@ -220,7 +222,7 @@ export function AddDomainSheet({ visible, tier, onClose, onAdd }: Props) { alignItems: 'center', gap: 10, padding: 12, - backgroundColor: '#f5f5f5', + backgroundColor: colors.surfaceElevated, borderRadius: 12, }} > @@ -235,7 +237,7 @@ export function AddDomainSheet({ visible, tier, onClose, onAdd }: Props) { flex: 1, fontSize: 14, fontFamily: 'Nunito_600SemiBold', - color: '#0a0a0a', + color: colors.text, }} numberOfLines={1} > @@ -289,8 +291,8 @@ export function AddDomainSheet({ visible, tier, onClose, onAdd }: Props) { height: 22, borderRadius: 6, borderWidth: 1.5, - borderColor: confirmPermanent ? '#16a34a' : '#d4d4d4', - backgroundColor: confirmPermanent ? '#16a34a' : '#fff', + borderColor: confirmPermanent ? colors.success : colors.border, + backgroundColor: confirmPermanent ? colors.success : colors.bg, alignItems: 'center', justifyContent: 'center', marginTop: 1, @@ -303,7 +305,7 @@ export function AddDomainSheet({ visible, tier, onClose, onAdd }: Props) { flex: 1, fontSize: 13, fontFamily: 'Nunito_400Regular', - color: '#0a0a0a', + color: colors.text, lineHeight: 18, }} > diff --git a/apps/rebreak-native/components/blocker/CooldownBanner.tsx b/apps/rebreak-native/components/blocker/CooldownBanner.tsx index 15391d4..4c294d1 100644 --- a/apps/rebreak-native/components/blocker/CooldownBanner.tsx +++ b/apps/rebreak-native/components/blocker/CooldownBanner.tsx @@ -2,6 +2,7 @@ import { View, Text, Pressable, ActivityIndicator } from 'react-native'; import { Ionicons } from '@expo/vector-icons'; import { useState } from 'react'; import { useTranslation } from 'react-i18next'; +import { useColors } from '../../lib/theme'; type Props = { remainingFormatted: string; // "23:59:42" @@ -10,6 +11,7 @@ type Props = { export function CooldownBanner({ remainingFormatted, onCancel }: Props) { const { t } = useTranslation(); + const colors = useColors(); const [cancelling, setCancelling] = useState(false); async function handleCancel() { diff --git a/apps/rebreak-native/components/blocker/DeactivationExplainerSheet.tsx b/apps/rebreak-native/components/blocker/DeactivationExplainerSheet.tsx index 97f77c0..df8c4ab 100644 --- a/apps/rebreak-native/components/blocker/DeactivationExplainerSheet.tsx +++ b/apps/rebreak-native/components/blocker/DeactivationExplainerSheet.tsx @@ -2,6 +2,7 @@ import { Modal, View, Text, Pressable, ScrollView, ActionSheetIOS, Platform, Ale import { Ionicons } from '@expo/vector-icons'; import { useState } from 'react'; import { useTranslation } from 'react-i18next'; +import { useColors } from '../../lib/theme'; type Props = { visible: boolean; @@ -26,6 +27,7 @@ export function DeactivationExplainerSheet({ onStartCooldown, }: Props) { const { t } = useTranslation(); + const colors = useColors(); const [submitting, setSubmitting] = useState(false); function showFinalConfirm() { @@ -74,7 +76,7 @@ export function DeactivationExplainerSheet({ presentationStyle="pageSheet" onRequestClose={onClose} > - + {/* Header */} - + {t('common.back')} - + {t('blocker.deactivation_heading')} - + {t('blocker.deactivation_title')} @@ -195,6 +197,7 @@ function BulletRow({ title: string; text: string; }) { + const colors = useColors(); return ( - + - + {title} = { export function DomainGrid({ domains, tier, onAdd, onSubmit, onUpgradePro }: Props) { const { t } = useTranslation(); + const colors = useColors(); // Slot-relevante Domains (alles außer approved). Sortiert nach Status-Priority, // innerhalb gleicher Priority dann newest-first by addedAt. const visible = useMemo(() => { @@ -85,7 +87,7 @@ export function DomainGrid({ domains, tier, onAdd, onSubmit, onUpgradePro }: Pro {/* Header: Section-Title + Slot-Counter + Add-Button (inline, neben SlotPill) */} - + {t('blocker.domain_section_title')} @@ -122,7 +124,7 @@ export function DomainGrid({ domains, tier, onAdd, onSubmit, onUpgradePro }: Pro const pct = (tier.usedSlots / tier.domainLimit) * 100; const barColor = pct >= 90 ? '#dc2626' : pct >= 60 ? '#f59e0b' : '#16a34a'; return ( - + - + Promise<{ ok: boolean }>; }) { const { t } = useTranslation(); + const colors = useColors(); const [submitting, setSubmitting] = useState(false); const [imgError, setImgError] = useState(false); const [successVisible, setSuccessVisible] = useState(false); @@ -346,9 +350,9 @@ function DomainTile({ return ( {t('blocker.protection_card_title')} @@ -76,7 +77,7 @@ export function ProtectionCard({ state, loading, onActivate, onPressSettings }: style={{ fontSize: 12, fontFamily: 'Nunito_400Regular', - color: '#525252', + color: colors.textMuted, marginTop: 2, }} > @@ -100,7 +101,7 @@ export function ProtectionCard({ state, loading, onActivate, onPressSettings }: width: 36, height: 36, borderRadius: 18, - backgroundColor: '#ffffff', + backgroundColor: colors.surface, alignItems: 'center', justifyContent: 'center', shadowColor: '#000', @@ -108,7 +109,7 @@ export function ProtectionCard({ state, loading, onActivate, onPressSettings }: shadowOpacity: 0.05, shadowRadius: 2, }}> - + ) : ( @@ -146,18 +147,19 @@ export function ProtectionCard({ state, loading, onActivate, onPressSettings }: function Stat({ label, value, - valueColor = '#0a0a0a', + valueColor, }: { label: string; value: string; valueColor?: string; }) { + const colors = useColors(); return ( - + {value} - + {label} diff --git a/apps/rebreak-native/components/blocker/ProtectionDetailsSheet.tsx b/apps/rebreak-native/components/blocker/ProtectionDetailsSheet.tsx index add2fa2..2995249 100644 --- a/apps/rebreak-native/components/blocker/ProtectionDetailsSheet.tsx +++ b/apps/rebreak-native/components/blocker/ProtectionDetailsSheet.tsx @@ -16,6 +16,7 @@ import { useTranslation } from 'react-i18next'; import Svg, { Path, Circle } from 'react-native-svg'; import type { ProtectionState } from '../../lib/protection'; import { apiFetch } from '../../lib/api'; +import { useColors } from '../../lib/theme'; type Props = { visible: boolean; @@ -55,6 +56,7 @@ export function ProtectionDetailsSheet({ onRequestDeactivation, }: Props) { const { t, i18n } = useTranslation(); + const colors = useColors(); const localeTag = i18n.language === 'de' ? 'de-DE' : 'en-US'; const sheetHeight = useRef(new Animated.Value(DEFAULT_HEIGHT)).current; @@ -162,7 +164,7 @@ export function ProtectionDetailsSheet({ - + {/* Header */} @@ -192,15 +194,15 @@ export function ProtectionDetailsSheet({ paddingTop: 4, paddingBottom: 12, borderBottomWidth: 1, - borderBottomColor: '#f0f0f0', + borderBottomColor: colors.border, }} > - + {t('blocker.details_title')} - + {t('blocker.details_done')} @@ -249,16 +251,16 @@ export function ProtectionDetailsSheet({ padding: 18, borderRadius: 16, borderWidth: 1, - borderColor: '#e5e5e5', - backgroundColor: '#fff', + borderColor: colors.border, + backgroundColor: colors.bg, gap: 8, }} > - + {t('blocker.kpi_submissions_title')} - + {t('blocker.kpi_submissions_subtitle')} @@ -323,14 +325,14 @@ export function ProtectionDetailsSheet({ flex: 1, fontSize: 11, fontFamily: 'Nunito_700Bold', - color: '#737373', + color: colors.textMuted, letterSpacing: 0.5, textTransform: 'uppercase', }} > {t('blocker.faq_heading')} - + {[1, 2, 3, 4].map((n) => ( - - + + {label} @@ -502,10 +505,10 @@ function KpiCard({ value={value} locale={locale} decimals={decimals} - style={{ fontSize: 18, fontFamily: 'Nunito_900Black', color: '#0a0a0a', letterSpacing: -0.3 }} + style={{ fontSize: 18, fontFamily: 'Nunito_900Black', color: colors.text, letterSpacing: -0.3 }} /> {suffix ? ( - {suffix} + {suffix} ) : null} @@ -522,13 +525,14 @@ function LegendItem({ label: string; value: number; }) { + const colors = useColors(); return ( - {value} + {value} - {label} + {label} ); } @@ -543,6 +547,7 @@ function HalfDonut({ centerValue: number; centerLabel: string; }) { + const colors = useColors(); const total = Math.max(1, segments.reduce((s, x) => s + x.value, 0)); const W = 220; @@ -582,7 +587,7 @@ function HalfDonut({ {/* Background track */} - + {centerValue} - + {centerLabel} @@ -643,6 +648,7 @@ function polar(cx: number, cy: number, r: number, angleDeg: number) { // ─── FAQ Item (chevron AT END of header row, on right) ───────────────────── function FaqItem({ question, answer }: { question: string; answer: string }) { + const colors = useColors(); const [open, setOpen] = useState(false); const rotateAnim = useRef(new Animated.Value(0)).current; @@ -664,10 +670,10 @@ function FaqItem({ question, answer }: { question: string; answer: string }) { style={{ alignSelf: 'stretch', borderWidth: 1, - borderColor: '#e5e5e5', + borderColor: colors.border, borderRadius: 12, overflow: 'hidden', - backgroundColor: '#fff', + backgroundColor: colors.bg, }} > - + {question} @@ -687,19 +693,19 @@ function FaqItem({ question, answer }: { question: string; answer: string }) { width: 28, height: 28, borderRadius: 14, - backgroundColor: '#f5f5f5', + backgroundColor: colors.surfaceElevated, alignItems: 'center', justifyContent: 'center', transform: [{ rotate }], }} > - + {open && ( - + {answer} diff --git a/apps/rebreak-native/components/blocker/ProtectionLockedCard.tsx b/apps/rebreak-native/components/blocker/ProtectionLockedCard.tsx index 4d4ad5e..fe369a7 100644 --- a/apps/rebreak-native/components/blocker/ProtectionLockedCard.tsx +++ b/apps/rebreak-native/components/blocker/ProtectionLockedCard.tsx @@ -2,6 +2,7 @@ import { View, Text, Pressable } from 'react-native'; import { Ionicons } from '@expo/vector-icons'; import { useTranslation } from 'react-i18next'; import type { ProtectionState } from '../../lib/protection'; +import { useColors } from '../../lib/theme'; type Props = { state: ProtectionState; @@ -16,6 +17,7 @@ type Props = { */ export function ProtectionLockedCard({ state, onPressSettings }: Props) { const { t } = useTranslation(); + const colors = useColors(); const isCooldown = state.phase === 'cooldownActive'; const cardBg = isCooldown ? '#fef3c7' : '#dcfce7'; const cardBorder = isCooldown ? '#fcd34d' : '#86efac'; @@ -57,14 +59,14 @@ export function ProtectionLockedCard({ state, onPressSettings }: Props) { - + {t('blocker.protection_card_locked_title')} @@ -84,7 +86,7 @@ export function ProtectionLockedCard({ state, onPressSettings }: Props) { width: 36, height: 36, borderRadius: 18, - backgroundColor: '#ffffff', + backgroundColor: colors.surface, alignItems: 'center', justifyContent: 'center', shadowColor: '#000', @@ -92,7 +94,7 @@ export function ProtectionLockedCard({ state, onPressSettings }: Props) { shadowOpacity: 0.05, shadowRadius: 2, }}> - + @@ -118,11 +120,12 @@ export function ProtectionLockedCard({ state, onPressSettings }: Props) { ); } -function Stat({ label, value, valueColor = '#0a0a0a' }: { label: string; value: string; valueColor?: string }) { +function Stat({ label, value, valueColor }: { label: string; value: string; valueColor?: string }) { + const colors = useColors(); return ( - {value} - {label} + {value} + {label} ); } diff --git a/apps/rebreak-native/components/chat/ChatBubble.tsx b/apps/rebreak-native/components/chat/ChatBubble.tsx index 0fa07a9..67a9ea5 100644 --- a/apps/rebreak-native/components/chat/ChatBubble.tsx +++ b/apps/rebreak-native/components/chat/ChatBubble.tsx @@ -13,6 +13,7 @@ import * as Clipboard from 'expo-clipboard'; import { Ionicons } from '@expo/vector-icons'; import { useTranslation } from 'react-i18next'; import { resolveAvatar } from '../../lib/resolveAvatar'; +import { useColors } from '../../lib/theme'; export type ChatMsg = { id: string; @@ -63,6 +64,8 @@ export function ChatBubble({ onOpenImage, }: Props) { const { t } = useTranslation(); + const colors = useColors(); + const styles = makeStyles(colors); const [actionsOpen, setActionsOpen] = useState(false); const longPressTimer = useRef | null>(null); @@ -323,120 +326,122 @@ export function ChatBubble({ ); } -const styles = StyleSheet.create({ - row: { - flexDirection: 'row', - paddingHorizontal: 8, - }, - avatarSlot: { - width: 30, - marginRight: 4, - justifyContent: 'flex-end', - }, - avatar: { - width: 26, - height: 26, - borderRadius: 13, - backgroundColor: '#e5e5e5', - }, - bubbleCol: { - maxWidth: '78%', - }, - nickname: { - fontSize: 10, - fontFamily: 'Nunito_700Bold', - color: '#007AFF', - marginBottom: 2, - marginLeft: 10, - }, - bubble: { - borderRadius: 18, - paddingHorizontal: 12, - paddingVertical: 6, - shadowColor: '#000', - shadowOpacity: 0.05, - shadowRadius: 1, - shadowOffset: { width: 0, height: 1 }, - }, - bubbleOwn: { - backgroundColor: '#007AFF', - }, - bubbleOther: { - backgroundColor: '#ffffff', - borderWidth: StyleSheet.hairlineWidth, - borderColor: '#e5e5e5', - }, - replyPreview: { - borderLeftWidth: 3, - borderRadius: 8, - paddingHorizontal: 8, - paddingVertical: 4, - marginBottom: 4, - }, - imageWrap: { - borderRadius: 12, - overflow: 'hidden', - position: 'relative', - }, - image: { - width: 220, - height: 220, - backgroundColor: '#f5f5f5', - }, - imageTimeOverlay: { - position: 'absolute', - bottom: 6, - right: 6, - backgroundColor: 'rgba(0,0,0,0.5)', - borderRadius: 10, - paddingHorizontal: 6, - paddingVertical: 2, - flexDirection: 'row', - alignItems: 'center', - }, - content: { - fontSize: 14, - lineHeight: 20, - fontFamily: 'Nunito_400Regular', - }, - footer: { - position: 'absolute', - bottom: 4, - right: 8, - flexDirection: 'row', - alignItems: 'center', - }, - sheetBackdrop: { - flex: 1, - backgroundColor: 'rgba(0,0,0,0.5)', - justifyContent: 'flex-end', - }, - sheet: { - backgroundColor: '#fff', - borderTopLeftRadius: 20, - borderTopRightRadius: 20, - padding: 8, - paddingBottom: Platform.OS === 'ios' ? 32 : 16, - }, - sheetGrabber: { - width: 36, - height: 4, - borderRadius: 2, - backgroundColor: '#d4d4d4', - alignSelf: 'center', - marginBottom: 10, - }, - sheetItem: { - flexDirection: 'row', - alignItems: 'center', - paddingHorizontal: 16, - paddingVertical: 14, - borderRadius: 12, - }, - sheetText: { - fontSize: 15, - fontFamily: 'Nunito_600SemiBold', - color: '#171717', - marginLeft: 12, - }, -}); +function makeStyles(colors: ReturnType) { + return StyleSheet.create({ + row: { + flexDirection: 'row', + paddingHorizontal: 8, + }, + avatarSlot: { + width: 30, + marginRight: 4, + justifyContent: 'flex-end', + }, + avatar: { + width: 26, + height: 26, + borderRadius: 13, + backgroundColor: colors.surfaceElevated, + }, + bubbleCol: { + maxWidth: '78%', + }, + nickname: { + fontSize: 10, + fontFamily: 'Nunito_700Bold', + color: '#007AFF', + marginBottom: 2, + marginLeft: 10, + }, + bubble: { + borderRadius: 18, + paddingHorizontal: 12, + paddingVertical: 6, + shadowColor: '#000', + shadowOpacity: 0.05, + shadowRadius: 1, + shadowOffset: { width: 0, height: 1 }, + }, + bubbleOwn: { + backgroundColor: '#007AFF', + }, + bubbleOther: { + backgroundColor: colors.surface, + borderWidth: StyleSheet.hairlineWidth, + borderColor: colors.border, + }, + replyPreview: { + borderLeftWidth: 3, + borderRadius: 8, + paddingHorizontal: 8, + paddingVertical: 4, + marginBottom: 4, + }, + imageWrap: { + borderRadius: 12, + overflow: 'hidden', + position: 'relative', + }, + image: { + width: 220, + height: 220, + backgroundColor: colors.surfaceElevated, + }, + imageTimeOverlay: { + position: 'absolute', + bottom: 6, + right: 6, + backgroundColor: 'rgba(0,0,0,0.5)', + borderRadius: 10, + paddingHorizontal: 6, + paddingVertical: 2, + flexDirection: 'row', + alignItems: 'center', + }, + content: { + fontSize: 14, + lineHeight: 20, + fontFamily: 'Nunito_400Regular', + }, + footer: { + position: 'absolute', + bottom: 4, + right: 8, + flexDirection: 'row', + alignItems: 'center', + }, + sheetBackdrop: { + flex: 1, + backgroundColor: 'rgba(0,0,0,0.5)', + justifyContent: 'flex-end', + }, + sheet: { + backgroundColor: colors.bg, + borderTopLeftRadius: 20, + borderTopRightRadius: 20, + padding: 8, + paddingBottom: Platform.OS === 'ios' ? 32 : 16, + }, + sheetGrabber: { + width: 36, + height: 4, + borderRadius: 2, + backgroundColor: colors.border, + alignSelf: 'center', + marginBottom: 10, + }, + sheetItem: { + flexDirection: 'row', + alignItems: 'center', + paddingHorizontal: 16, + paddingVertical: 14, + borderRadius: 12, + }, + sheetText: { + fontSize: 15, + fontFamily: 'Nunito_600SemiBold', + color: colors.text, + marginLeft: 12, + }, + }); +} diff --git a/apps/rebreak-native/components/chat/ChatInput.tsx b/apps/rebreak-native/components/chat/ChatInput.tsx index e0dc47b..9c3ef5e 100644 --- a/apps/rebreak-native/components/chat/ChatInput.tsx +++ b/apps/rebreak-native/components/chat/ChatInput.tsx @@ -16,6 +16,7 @@ import * as FileSystem from 'expo-file-system/legacy'; import { Ionicons } from '@expo/vector-icons'; import { useTranslation } from 'react-i18next'; import { supabase } from '../../lib/supabase'; +import { useColors } from '../../lib/theme'; type ReplyTo = { id: string; nickname: string; content: string }; @@ -45,6 +46,7 @@ export function ChatInput({ onCancelReply, }: Props) { const { t } = useTranslation(); + const colors = useColors(); const [text, setText] = useState(''); const [attachment, setAttachment] = useState<{ uri: string; @@ -137,6 +139,8 @@ export function ChatInput({ setAttachment(null); } + const styles = makeStyles(colors); + return ( {/* Reply preview */} @@ -231,103 +235,105 @@ function decodeBase64(base64: string): Uint8Array { return bytes; } -const styles = StyleSheet.create({ - container: { - backgroundColor: '#ffffff', - borderTopWidth: StyleSheet.hairlineWidth, - borderTopColor: '#e5e5e5', - }, - replyBar: { - flexDirection: 'row', - alignItems: 'center', - paddingHorizontal: 12, - paddingVertical: 8, - backgroundColor: '#eff6ff', - borderLeftWidth: 3, - borderLeftColor: '#007AFF', - marginHorizontal: 8, - marginTop: 6, - borderRadius: 8, - }, - replyName: { - fontSize: 11, - fontFamily: 'Nunito_700Bold', - color: '#007AFF', - }, - replyContent: { - fontSize: 11, - fontFamily: 'Nunito_400Regular', - color: '#525252', - marginTop: 1, - }, - attachBar: { - flexDirection: 'row', - alignItems: 'center', - paddingHorizontal: 12, - paddingVertical: 6, - backgroundColor: '#fafafa', - marginHorizontal: 8, - marginTop: 6, - borderRadius: 8, - }, - attachImg: { - width: 36, - height: 36, - borderRadius: 6, - marginRight: 8, - }, - attachFileIcon: { - width: 36, - height: 36, - borderRadius: 6, - backgroundColor: '#e5e5e5', - alignItems: 'center', - justifyContent: 'center', - marginRight: 8, - }, - attachName: { - flex: 1, - fontSize: 12, - fontFamily: 'Nunito_600SemiBold', - color: '#171717', - }, - row: { - flexDirection: 'row', - alignItems: 'flex-end', - paddingHorizontal: 8, - paddingTop: 8, - paddingBottom: 8, - }, - iconBtn: { - width: 36, - height: 36, - borderRadius: 18, - alignItems: 'center', - justifyContent: 'center', - marginRight: 4, - }, - inputWrap: { - flex: 1, - backgroundColor: '#f5f5f5', - borderRadius: 22, - paddingHorizontal: 14, - minHeight: 36, - maxHeight: 120, - justifyContent: 'center', - }, - input: { - fontSize: 14, - lineHeight: 19, - fontFamily: 'Nunito_400Regular', - color: '#171717', - paddingVertical: Platform.OS === 'ios' ? 8 : 4, - }, - sendBtn: { - width: 36, - height: 36, - borderRadius: 18, - alignItems: 'center', - justifyContent: 'center', - marginLeft: 6, - }, -}); +function makeStyles(colors: ReturnType) { + return StyleSheet.create({ + container: { + backgroundColor: colors.bg, + borderTopWidth: StyleSheet.hairlineWidth, + borderTopColor: colors.border, + }, + replyBar: { + flexDirection: 'row', + alignItems: 'center', + paddingHorizontal: 12, + paddingVertical: 8, + backgroundColor: colors.surface, + borderLeftWidth: 3, + borderLeftColor: '#007AFF', + marginHorizontal: 8, + marginTop: 6, + borderRadius: 8, + }, + replyName: { + fontSize: 11, + fontFamily: 'Nunito_700Bold', + color: '#007AFF', + }, + replyContent: { + fontSize: 11, + fontFamily: 'Nunito_400Regular', + color: colors.textMuted, + marginTop: 1, + }, + attachBar: { + flexDirection: 'row', + alignItems: 'center', + paddingHorizontal: 12, + paddingVertical: 6, + backgroundColor: colors.surface, + marginHorizontal: 8, + marginTop: 6, + borderRadius: 8, + }, + attachImg: { + width: 36, + height: 36, + borderRadius: 6, + marginRight: 8, + }, + attachFileIcon: { + width: 36, + height: 36, + borderRadius: 6, + backgroundColor: colors.surfaceElevated, + alignItems: 'center', + justifyContent: 'center', + marginRight: 8, + }, + attachName: { + flex: 1, + fontSize: 12, + fontFamily: 'Nunito_600SemiBold', + color: colors.text, + }, + row: { + flexDirection: 'row', + alignItems: 'flex-end', + paddingHorizontal: 8, + paddingTop: 8, + paddingBottom: 8, + }, + iconBtn: { + width: 36, + height: 36, + borderRadius: 18, + alignItems: 'center', + justifyContent: 'center', + marginRight: 4, + }, + inputWrap: { + flex: 1, + backgroundColor: colors.surfaceElevated, + borderRadius: 22, + paddingHorizontal: 14, + minHeight: 36, + maxHeight: 120, + justifyContent: 'center', + }, + input: { + fontSize: 14, + lineHeight: 19, + fontFamily: 'Nunito_400Regular', + color: colors.text, + paddingVertical: Platform.OS === 'ios' ? 8 : 4, + }, + sendBtn: { + width: 36, + height: 36, + borderRadius: 18, + alignItems: 'center', + justifyContent: 'center', + marginLeft: 6, + }, + }); +} diff --git a/apps/rebreak-native/components/chat/CreateRoomSheet.tsx b/apps/rebreak-native/components/chat/CreateRoomSheet.tsx index 5b9be05..956e058 100644 --- a/apps/rebreak-native/components/chat/CreateRoomSheet.tsx +++ b/apps/rebreak-native/components/chat/CreateRoomSheet.tsx @@ -12,6 +12,7 @@ import { import { Ionicons } from '@expo/vector-icons'; import { useTranslation } from 'react-i18next'; import { apiFetch } from '../../lib/api'; +import { useColors } from '../../lib/theme'; type Props = { visible: boolean; @@ -21,6 +22,8 @@ type Props = { export function CreateRoomSheet({ visible, onClose, onCreated }: Props) { const { t } = useTranslation(); + const colors = useColors(); + const styles = makeStyles(colors); const [name, setName] = useState(''); const [description, setDescription] = useState(''); const [isPublic, setIsPublic] = useState(true); @@ -145,138 +148,140 @@ export function CreateRoomSheet({ visible, onClose, onCreated }: Props) { ); } -const styles = StyleSheet.create({ - backdrop: { - flex: 1, - backgroundColor: 'rgba(0,0,0,0.5)', - justifyContent: 'flex-end', - }, - sheet: { - backgroundColor: '#fff', - borderTopLeftRadius: 22, - borderTopRightRadius: 22, - padding: 18, - paddingBottom: Platform.OS === 'ios' ? 32 : 18, - }, - grabber: { - width: 36, - height: 4, - borderRadius: 2, - backgroundColor: '#d4d4d4', - alignSelf: 'center', - marginBottom: 12, - }, - title: { - fontSize: 17, - fontFamily: 'Nunito_700Bold', - color: '#171717', - marginBottom: 14, - }, - input: { - backgroundColor: '#f5f5f5', - borderRadius: 12, - paddingHorizontal: 14, - paddingVertical: 12, - fontSize: 14, - fontFamily: 'Nunito_400Regular', - color: '#171717', - marginBottom: 10, - }, - toggleRow: { - flexDirection: 'row', - alignItems: 'center', - justifyContent: 'space-between', - paddingVertical: 6, - marginTop: 4, - }, - toggleLabel: { - fontSize: 14, - fontFamily: 'Nunito_600SemiBold', - color: '#171717', - }, - toggle: { - width: 46, - height: 28, - borderRadius: 14, - backgroundColor: '#e5e5e5', - padding: 2, - justifyContent: 'center', - }, - toggleOn: { - backgroundColor: '#007AFF', - }, - toggleKnob: { - width: 24, - height: 24, - borderRadius: 12, - backgroundColor: '#fff', - shadowColor: '#000', - shadowOpacity: 0.15, - shadowRadius: 2, - shadowOffset: { width: 0, height: 1 }, - elevation: 2, - }, - toggleKnobOn: { - transform: [{ translateX: 18 }], - }, - subLabel: { - fontSize: 12, - fontFamily: 'Nunito_600SemiBold', - color: '#737373', - marginBottom: 6, - }, - modeRow: { - flexDirection: 'row', - }, - modeBtn: { - flex: 1, - paddingVertical: 8, - borderRadius: 10, - borderWidth: 1, - borderColor: '#e5e5e5', - alignItems: 'center', - marginRight: 6, - }, - modeBtnActive: { - backgroundColor: '#eff6ff', - borderColor: '#007AFF', - }, - modeBtnText: { - fontSize: 12, - fontFamily: 'Nunito_600SemiBold', - color: '#737373', - }, - modeBtnTextActive: { - color: '#007AFF', - }, - actions: { - flexDirection: 'row', - marginTop: 20, - }, - cancelBtn: { - flex: 1, - backgroundColor: '#f5f5f5', - paddingVertical: 12, - borderRadius: 12, - alignItems: 'center', - marginRight: 6, - }, - cancelText: { - fontSize: 14, - fontFamily: 'Nunito_600SemiBold', - color: '#171717', - }, - createBtn: { - flex: 1, - backgroundColor: '#007AFF', - paddingVertical: 12, - borderRadius: 12, - alignItems: 'center', - marginLeft: 6, - }, - createText: { - fontSize: 14, - fontFamily: 'Nunito_700Bold', - color: '#fff', - }, -}); +function makeStyles(colors: ReturnType) { + return StyleSheet.create({ + backdrop: { + flex: 1, + backgroundColor: 'rgba(0,0,0,0.5)', + justifyContent: 'flex-end', + }, + sheet: { + backgroundColor: colors.bg, + borderTopLeftRadius: 22, + borderTopRightRadius: 22, + padding: 18, + paddingBottom: Platform.OS === 'ios' ? 32 : 18, + }, + grabber: { + width: 36, + height: 4, + borderRadius: 2, + backgroundColor: colors.border, + alignSelf: 'center', + marginBottom: 12, + }, + title: { + fontSize: 17, + fontFamily: 'Nunito_700Bold', + color: colors.text, + marginBottom: 14, + }, + input: { + backgroundColor: colors.surfaceElevated, + borderRadius: 12, + paddingHorizontal: 14, + paddingVertical: 12, + fontSize: 14, + fontFamily: 'Nunito_400Regular', + color: colors.text, + marginBottom: 10, + }, + toggleRow: { + flexDirection: 'row', + alignItems: 'center', + justifyContent: 'space-between', + paddingVertical: 6, + marginTop: 4, + }, + toggleLabel: { + fontSize: 14, + fontFamily: 'Nunito_600SemiBold', + color: colors.text, + }, + toggle: { + width: 46, + height: 28, + borderRadius: 14, + backgroundColor: colors.surfaceElevated, + padding: 2, + justifyContent: 'center', + }, + toggleOn: { + backgroundColor: '#007AFF', + }, + toggleKnob: { + width: 24, + height: 24, + borderRadius: 12, + backgroundColor: colors.bg, + shadowColor: '#000', + shadowOpacity: 0.15, + shadowRadius: 2, + shadowOffset: { width: 0, height: 1 }, + elevation: 2, + }, + toggleKnobOn: { + transform: [{ translateX: 18 }], + }, + subLabel: { + fontSize: 12, + fontFamily: 'Nunito_600SemiBold', + color: colors.textMuted, + marginBottom: 6, + }, + modeRow: { + flexDirection: 'row', + }, + modeBtn: { + flex: 1, + paddingVertical: 8, + borderRadius: 10, + borderWidth: 1, + borderColor: colors.border, + alignItems: 'center', + marginRight: 6, + }, + modeBtnActive: { + backgroundColor: colors.surface, + borderColor: '#007AFF', + }, + modeBtnText: { + fontSize: 12, + fontFamily: 'Nunito_600SemiBold', + color: colors.textMuted, + }, + modeBtnTextActive: { + color: '#007AFF', + }, + actions: { + flexDirection: 'row', + marginTop: 20, + }, + cancelBtn: { + flex: 1, + backgroundColor: colors.surfaceElevated, + paddingVertical: 12, + borderRadius: 12, + alignItems: 'center', + marginRight: 6, + }, + cancelText: { + fontSize: 14, + fontFamily: 'Nunito_600SemiBold', + color: colors.text, + }, + createBtn: { + flex: 1, + backgroundColor: '#007AFF', + paddingVertical: 12, + borderRadius: 12, + alignItems: 'center', + marginLeft: 6, + }, + createText: { + fontSize: 14, + fontFamily: 'Nunito_700Bold', + color: '#fff', + }, + }); +} diff --git a/apps/rebreak-native/components/chat/RoomCard.tsx b/apps/rebreak-native/components/chat/RoomCard.tsx index 1afbac5..5ef7e86 100644 --- a/apps/rebreak-native/components/chat/RoomCard.tsx +++ b/apps/rebreak-native/components/chat/RoomCard.tsx @@ -1,6 +1,7 @@ import { View, Text, Pressable, Image, StyleSheet } from 'react-native'; import { Ionicons } from '@expo/vector-icons'; import { useTranslation } from 'react-i18next'; +import { useColors } from '../../lib/theme'; export type Room = { id: string; @@ -29,6 +30,8 @@ function formatTime(ts: string, justNow: string) { export function RoomCard({ room, onPress }: Props) { const { t } = useTranslation(); + const colors = useColors(); + const styles = makeStyles(colors); const initials = room.name .split(' ') .slice(0, 2) @@ -42,7 +45,7 @@ export function RoomCard({ room, onPress }: Props) { {room.avatarUrl ? ( @@ -102,116 +105,118 @@ export function RoomCard({ room, onPress }: Props) { ); } -const styles = StyleSheet.create({ - row: { - width: '100%', - flexDirection: 'row', - alignItems: 'center', - paddingHorizontal: 14, - paddingVertical: 11, - backgroundColor: '#fff', - borderBottomWidth: StyleSheet.hairlineWidth, - borderBottomColor: '#f5f5f5', - }, - avatar: { - width: 42, - height: 42, - borderRadius: 21, - alignItems: 'center', - justifyContent: 'center', - overflow: 'hidden', - marginRight: 10, - }, - avatarImg: { - width: 42, - height: 42, - }, - avatarInitials: { - fontSize: 13, - fontFamily: 'Nunito_700Bold', - color: '#525252', - }, - info: { - flex: 1, - minWidth: 0, - }, - headerRow: { - flexDirection: 'row', - alignItems: 'center', - }, - footerRow: { - flexDirection: 'row', - alignItems: 'center', - marginTop: 3, - }, - footerTextWrap: { - flex: 1, - minWidth: 0, - }, - metaPill: { - flexDirection: 'row', - alignItems: 'center', - marginLeft: 8, - paddingHorizontal: 6, - paddingVertical: 2, - borderRadius: 8, - backgroundColor: '#f5f5f5', - }, - name: { - fontSize: 14, - fontFamily: 'Nunito_700Bold', - color: '#171717', - flexShrink: 1, - }, - defaultBadge: { - marginLeft: 6, - paddingHorizontal: 6, - paddingVertical: 1, - backgroundColor: '#eff6ff', - borderRadius: 8, - }, - defaultBadgeText: { - fontSize: 9, - fontFamily: 'Nunito_700Bold', - color: '#007AFF', - }, - lastMessage: { - fontSize: 12, - fontFamily: 'Nunito_400Regular', - color: '#737373', - }, - description: { - fontSize: 12, - fontFamily: 'Nunito_400Regular', - color: '#a3a3a3', - }, - right: { - alignItems: 'flex-end', - marginLeft: 8, - }, - memberCount: { - fontSize: 11, - fontFamily: 'Nunito_700Bold', - color: '#737373', - marginLeft: 3, - }, - time: { - fontSize: 10, - fontFamily: 'Nunito_500Medium', - color: '#a3a3a3', - marginLeft: 'auto', - paddingLeft: 6, - }, - joinBadge: { - marginLeft: 6, - paddingHorizontal: 8, - paddingVertical: 3, - backgroundColor: '#eff6ff', - borderRadius: 10, - }, - joinBadgeText: { - fontSize: 10, - fontFamily: 'Nunito_700Bold', - color: '#007AFF', - }, -}); +function makeStyles(colors: ReturnType) { + return StyleSheet.create({ + row: { + width: '100%', + flexDirection: 'row', + alignItems: 'center', + paddingHorizontal: 14, + paddingVertical: 11, + backgroundColor: colors.bg, + borderBottomWidth: StyleSheet.hairlineWidth, + borderBottomColor: colors.border, + }, + avatar: { + width: 42, + height: 42, + borderRadius: 21, + alignItems: 'center', + justifyContent: 'center', + overflow: 'hidden', + marginRight: 10, + }, + avatarImg: { + width: 42, + height: 42, + }, + avatarInitials: { + fontSize: 13, + fontFamily: 'Nunito_700Bold', + color: colors.textMuted, + }, + info: { + flex: 1, + minWidth: 0, + }, + headerRow: { + flexDirection: 'row', + alignItems: 'center', + }, + footerRow: { + flexDirection: 'row', + alignItems: 'center', + marginTop: 3, + }, + footerTextWrap: { + flex: 1, + minWidth: 0, + }, + metaPill: { + flexDirection: 'row', + alignItems: 'center', + marginLeft: 8, + paddingHorizontal: 6, + paddingVertical: 2, + borderRadius: 8, + backgroundColor: colors.surfaceElevated, + }, + name: { + fontSize: 14, + fontFamily: 'Nunito_700Bold', + color: colors.text, + flexShrink: 1, + }, + defaultBadge: { + marginLeft: 6, + paddingHorizontal: 6, + paddingVertical: 1, + backgroundColor: colors.surface, + borderRadius: 8, + }, + defaultBadgeText: { + fontSize: 9, + fontFamily: 'Nunito_700Bold', + color: '#007AFF', + }, + lastMessage: { + fontSize: 12, + fontFamily: 'Nunito_400Regular', + color: colors.textMuted, + }, + description: { + fontSize: 12, + fontFamily: 'Nunito_400Regular', + color: colors.textMuted, + }, + right: { + alignItems: 'flex-end', + marginLeft: 8, + }, + memberCount: { + fontSize: 11, + fontFamily: 'Nunito_700Bold', + color: colors.textMuted, + marginLeft: 3, + }, + time: { + fontSize: 10, + fontFamily: 'Nunito_500Medium', + color: colors.textMuted, + marginLeft: 'auto', + paddingLeft: 6, + }, + joinBadge: { + marginLeft: 6, + paddingHorizontal: 8, + paddingVertical: 3, + backgroundColor: colors.surface, + borderRadius: 10, + }, + joinBadgeText: { + fontSize: 10, + fontFamily: 'Nunito_700Bold', + color: '#007AFF', + }, + }); +} diff --git a/apps/rebreak-native/components/mail/ConnectMailSheet.tsx b/apps/rebreak-native/components/mail/ConnectMailSheet.tsx index 8c8a4d7..be7405b 100644 --- a/apps/rebreak-native/components/mail/ConnectMailSheet.tsx +++ b/apps/rebreak-native/components/mail/ConnectMailSheet.tsx @@ -18,6 +18,7 @@ import { useSafeAreaInsets } from 'react-native-safe-area-context'; import { Ionicons } from '@expo/vector-icons'; import { useTranslation } from 'react-i18next'; import { useMailConnect, detectProvider, type MailProvider } from '../../hooks/useMailConnect'; +import { useColors } from '../../lib/theme'; const SCREEN_HEIGHT = Dimensions.get('window').height; const SHEET_HEIGHT = SCREEN_HEIGHT * 0.65; @@ -97,6 +98,7 @@ const PROVIDERS: ProviderConfig[] = [ */ export function ConnectMailSheet({ visible, onClose, onSuccess }: Props) { const { t } = useTranslation(); + const colors = useColors(); const insets = useSafeAreaInsets(); const { connect, connecting, error: connectError } = useMailConnect(); @@ -203,7 +205,7 @@ export function ConnectMailSheet({ visible, onClose, onSuccess }: Props) { right: 0, bottom: 0, height: SHEET_HEIGHT, - backgroundColor: '#fff', + backgroundColor: colors.bg, borderTopLeftRadius: 20, borderTopRightRadius: 20, transform: [{ translateY }], @@ -215,7 +217,7 @@ export function ConnectMailSheet({ visible, onClose, onSuccess }: Props) { > {/* Drag-Handle */} - + {/* Header */} @@ -228,24 +230,24 @@ export function ConnectMailSheet({ visible, onClose, onSuccess }: Props) { paddingTop: 6, paddingBottom: 12, borderBottomWidth: 1, - borderBottomColor: '#f0f0f0', + borderBottomColor: colors.border, }} > {view === 'form' ? ( - + {t('common.back')} ) : ( - + {t('common.cancel')} )} - + {view === 'form' && currentProvider ? t(currentProvider.labelKey) : t('mail.connect_sheet_title')} @@ -293,6 +295,7 @@ function ProviderGrid({ onSelect: (p: ProviderConfig) => void; t: (key: string) => string; }) { + const colors = useColors(); return ( @@ -345,13 +348,13 @@ function ProviderGrid({ {t(p.labelKey)} - + ))} @@ -394,6 +397,7 @@ function FormView({ insets, t, }: FormViewProps) { + const colors = useColors(); const canConnect = email.trim().length > 0 && password.trim().length > 0 && !connecting; return ( @@ -461,7 +465,7 @@ function FormView({ style={{ fontSize: 12, fontFamily: 'Nunito_600SemiBold', - color: '#525252', + color: colors.textMuted, marginBottom: 6, }} > @@ -471,19 +475,19 @@ function FormView({ value={email} onChangeText={onEmailChange} placeholder={t('mail.form_email_placeholder')} - placeholderTextColor="#a3a3a3" + placeholderTextColor={colors.textMuted} autoCapitalize="none" autoCorrect={false} keyboardType="email-address" returnKeyType="next" style={{ - backgroundColor: '#f5f5f5', + backgroundColor: colors.surfaceElevated, borderRadius: 12, paddingHorizontal: 14, paddingVertical: 12, fontSize: 15, fontFamily: 'Nunito_400Regular', - color: '#0a0a0a', + color: colors.text, }} /> @@ -494,7 +498,7 @@ function FormView({ style={{ fontSize: 12, fontFamily: 'Nunito_600SemiBold', - color: '#525252', + color: colors.textMuted, marginBottom: 6, }} > @@ -505,21 +509,21 @@ function FormView({ value={password} onChangeText={onPasswordChange} placeholder={t('mail.form_password_placeholder')} - placeholderTextColor="#a3a3a3" + placeholderTextColor={colors.textMuted} secureTextEntry={!passwordVisible} autoCapitalize="none" autoCorrect={false} returnKeyType="done" onSubmitEditing={onConnect} style={{ - backgroundColor: '#f5f5f5', + backgroundColor: colors.surfaceElevated, borderRadius: 12, paddingHorizontal: 14, paddingVertical: 12, paddingRight: 46, fontSize: 15, fontFamily: 'Nunito_400Regular', - color: '#0a0a0a', + color: colors.text, }} /> {/* Drag-Handle */} - + {/* Header */} @@ -129,15 +131,15 @@ export function EditMailAccountSheet({ visible, email, onClose, onSuccess }: Pro paddingTop: 6, paddingBottom: 12, borderBottomWidth: 1, - borderBottomColor: '#f0f0f0', + borderBottomColor: colors.border, }} > - + {t('common.cancel')} - + {t('mail.edit_account_title')} @@ -148,7 +150,7 @@ export function EditMailAccountSheet({ visible, email, onClose, onSuccess }: Pro style={{ fontSize: 13, fontFamily: 'Nunito_400Regular', - color: '#737373', + color: colors.textMuted, lineHeight: 18, }} > @@ -159,13 +161,13 @@ export function EditMailAccountSheet({ visible, email, onClose, onSuccess }: Pro style={{ flexDirection: 'row', alignItems: 'center', - backgroundColor: '#f5f5f5', + backgroundColor: colors.surfaceElevated, borderRadius: 12, paddingHorizontal: 14, gap: 10, }} > - + { @@ -173,7 +175,7 @@ export function EditMailAccountSheet({ visible, email, onClose, onSuccess }: Pro setFormError(null); }} placeholder={t('mail.app_password_placeholder')} - placeholderTextColor="#a3a3a3" + placeholderTextColor={colors.textMuted} secureTextEntry={!passwordVisible} autoCapitalize="none" autoCorrect={false} @@ -182,7 +184,7 @@ export function EditMailAccountSheet({ visible, email, onClose, onSuccess }: Pro paddingVertical: 14, fontSize: 15, fontFamily: 'Nunito_400Regular', - color: '#0a0a0a', + color: colors.text, }} /> setPasswordVisible((p) => !p)} hitSlop={8}> diff --git a/apps/rebreak-native/components/mail/MailEmptyState.tsx b/apps/rebreak-native/components/mail/MailEmptyState.tsx index 36fb6f7..c6765e0 100644 --- a/apps/rebreak-native/components/mail/MailEmptyState.tsx +++ b/apps/rebreak-native/components/mail/MailEmptyState.tsx @@ -1,6 +1,7 @@ import { Pressable, Text, View } from 'react-native'; import { Ionicons } from '@expo/vector-icons'; import { useTranslation } from 'react-i18next'; +import { useColors } from '../../lib/theme'; type Props = { onConnectPress: () => void; @@ -12,14 +13,15 @@ type Props = { */ export function MailEmptyState({ onConnectPress }: Props) { const { t } = useTranslation(); + const colors = useColors(); return ( ( - + {t(`mail.${key}`)} diff --git a/apps/rebreak-native/components/profile/DigaMissionBanner.tsx b/apps/rebreak-native/components/profile/DigaMissionBanner.tsx index b31f9cd..1aa68bd 100644 --- a/apps/rebreak-native/components/profile/DigaMissionBanner.tsx +++ b/apps/rebreak-native/components/profile/DigaMissionBanner.tsx @@ -1,6 +1,6 @@ import { View, Text, Pressable } from 'react-native'; import { Ionicons } from '@expo/vector-icons'; -import { colors } from '../../lib/theme'; +import { useColors } from '../../lib/theme'; type Props = { onDismiss?: () => void; @@ -8,6 +8,7 @@ type Props = { }; export function DigaMissionBanner({ onDismiss, onContribute }: Props) { + const colors = useColors(); return ( void; onSpeak?: (text: string) => Promise | void }; export function BreathingCard({ onDone, onSpeak }: Props) { + const colors = useColors(); const [breathState, setBreathState] = useState('idle'); const [countdown, setCountdown] = useState(3); const [round, setRound] = useState(1); @@ -86,7 +87,7 @@ export function BreathingCard({ onDone, onSpeak }: Props) { 4-7-8 Atemübung 3 Runden · beruhigt dein Nervensystem - { setCountdown(3); setBreathState('countdown'); }}> + { setCountdown(3); setBreathState('countdown'); }}> Starten @@ -114,6 +115,7 @@ export function BreathingCard({ onDone, onSpeak }: Props) { // ── BreathingDrawer (bottom sheet, covers input, slides up) ─────────────────── export function BreathingDrawer({ onDone, onSpeak }: Props) { + const colors = useColors(); const slideAnim = useRef(new Animated.Value(500)).current; useEffect(() => { Animated.spring(slideAnim, { toValue: 0, useNativeDriver: true, damping: 22, mass: 1, stiffness: 200 }).start(); @@ -121,7 +123,7 @@ export function BreathingDrawer({ onDone, onSpeak }: Props) { return ( <> - + @@ -131,14 +133,14 @@ export function BreathingDrawer({ onDone, onSpeak }: Props) { const st = StyleSheet.create({ breathBackdrop: { position: 'absolute', top: 0, left: 0, right: 0, bottom: 0, backgroundColor: 'rgba(0,0,0,0.28)', zIndex: 20 }, - breathDrawerContainer: { position: 'absolute', bottom: 0, left: 0, right: 0, zIndex: 21, backgroundColor: '#ffffff', borderTopLeftRadius: 28, borderTopRightRadius: 28, paddingBottom: 36, shadowColor: '#000', shadowOffset: { width: 0, height: -4 }, shadowOpacity: 0.18, shadowRadius: 20, elevation: 24 }, + breathDrawerContainer: { position: 'absolute', bottom: 0, left: 0, right: 0, zIndex: 21, borderTopLeftRadius: 28, borderTopRightRadius: 28, paddingBottom: 36, shadowColor: '#000', shadowOffset: { width: 0, height: -4 }, shadowOpacity: 0.18, shadowRadius: 20, elevation: 24 }, breathDrawerHandle: { width: 40, height: 4, borderRadius: 2, backgroundColor: '#d1d5db', alignSelf: 'center', marginTop: 14, marginBottom: 4 }, breathCardInner: { paddingHorizontal: 24, paddingTop: 20, paddingBottom: 8, alignItems: 'center', gap: 16 }, breathCircleLg: { width: 190, height: 190, borderRadius: 95, alignItems: 'center', justifyContent: 'center', borderWidth: 5 }, breathCountLg: { fontFamily: 'Nunito_800ExtraBold', fontSize: 60, color: '#111827', lineHeight: 68 }, breathTitle: { fontFamily: 'Nunito_700Bold', fontSize: 15, color: '#111827' }, breathSub: { fontFamily: 'Nunito_400Regular', fontSize: 13, color: '#6b7280', textAlign: 'center' }, - breathStartBtn: { borderRadius: 12, backgroundColor: colors.brandOrange, paddingHorizontal: 28, paddingVertical: 10, marginTop: 4 }, + breathStartBtn: { borderRadius: 12, paddingHorizontal: 28, paddingVertical: 10, marginTop: 4 }, breathStartTxt: { color: '#fff', fontFamily: 'Nunito_700Bold', fontSize: 14 }, breathRound: { fontFamily: 'Nunito_600SemiBold', fontSize: 12, color: '#9ca3af' }, breathPhaseLabel: { fontFamily: 'Nunito_700Bold', fontSize: 13 }, diff --git a/apps/rebreak-native/components/urge/InlineRatingDrawer.tsx b/apps/rebreak-native/components/urge/InlineRatingDrawer.tsx index da2d657..a063a58 100644 --- a/apps/rebreak-native/components/urge/InlineRatingDrawer.tsx +++ b/apps/rebreak-native/components/urge/InlineRatingDrawer.tsx @@ -11,7 +11,7 @@ import { ScrollView, } from 'react-native'; import { Ionicons } from '@expo/vector-icons'; -import { colors } from '../../lib/theme'; +import { useColors } from '../../lib/theme'; import type { SosFeedback } from './SosFeedbackModal'; /** @@ -25,6 +25,7 @@ export function InlineRatingDrawer({ onSubmit: (feedback: SosFeedback) => Promise | void; onClose: () => void; }) { + const colors = useColors(); const slide = useRef(new Animated.Value(600)).current; const [better, setBetter] = useState(null); const [rating, setRating] = useState(0); @@ -58,7 +59,7 @@ export function InlineRatingDrawer({ return ( <> - + - Bewerte diese Session + Bewerte diese Session - + Dein Feedback hilft uns, Lyra besser zu machen. - Fühlst du dich besser? + Fühlst du dich besser? - Bewertung + Bewertung {[1, 2, 3, 4, 5].map((n) => ( setRating(n)} hitSlop={6}> @@ -116,9 +117,9 @@ export function InlineRatingDrawer({ ))} - Bemerkung (optional) + Bemerkung (optional) Abbrechen @@ -199,7 +200,7 @@ const s = StyleSheet.create({ cancelTxt: { fontFamily: 'Nunito_700Bold', fontSize: 14, color: '#475569' }, submitBtn: { flex: 2, paddingVertical: 12, borderRadius: 12, - alignItems: 'center', backgroundColor: colors.brandOrange, + alignItems: 'center', }, submitTxt: { fontFamily: 'Nunito_800ExtraBold', fontSize: 14, color: '#fff' }, }); diff --git a/apps/rebreak-native/components/urge/ShareSuccessDrawer.tsx b/apps/rebreak-native/components/urge/ShareSuccessDrawer.tsx index 2e00899..d470403 100644 --- a/apps/rebreak-native/components/urge/ShareSuccessDrawer.tsx +++ b/apps/rebreak-native/components/urge/ShareSuccessDrawer.tsx @@ -12,7 +12,7 @@ import { ScrollView, } from 'react-native'; import { Ionicons } from '@expo/vector-icons'; -import { colors } from '../../lib/theme'; +import { useColors } from '../../lib/theme'; export interface ShareSuccessPayload { text: string; @@ -35,6 +35,7 @@ export function ShareSuccessDrawer({ onClose: () => void; onRegenerate?: () => void; }) { + const colors = useColors(); const slide = useRef(new Animated.Value(600)).current; const [text, setText] = useState(initialText); const [submitting, setSubmitting] = useState(false); @@ -67,7 +68,7 @@ export function ShareSuccessDrawer({ return ( <> - + - Erfolg teilen + Erfolg teilen - + Inspiriere andere — dein Beitrag wird anonym in der Community gepostet. @@ -93,7 +94,7 @@ export function ShareSuccessDrawer({ ) : ( Abbrechen @@ -203,7 +204,6 @@ const s = StyleSheet.create({ shareBtn: { flex: 1, minWidth: 110, flexDirection: 'row', alignItems: 'center', justifyContent: 'center', gap: 6, - backgroundColor: colors.brandOrange, borderRadius: 12, paddingVertical: 12, }, shareBtnDisabled: { opacity: 0.5 }, diff --git a/apps/rebreak-native/components/urge/SosFeedbackModal.tsx b/apps/rebreak-native/components/urge/SosFeedbackModal.tsx index 85f57fe..d6032c7 100644 --- a/apps/rebreak-native/components/urge/SosFeedbackModal.tsx +++ b/apps/rebreak-native/components/urge/SosFeedbackModal.tsx @@ -1,7 +1,7 @@ import { useState } from 'react'; import { View, Text, Pressable, TextInput, Modal, StyleSheet, Platform, KeyboardAvoidingView, ScrollView } from 'react-native'; import { Ionicons } from '@expo/vector-icons'; -import { colors } from '../../lib/theme'; +import { useColors } from '../../lib/theme'; export interface SosFeedback { better: boolean | null; @@ -18,6 +18,7 @@ export function SosFeedbackModal({ onSubmit: (feedback: SosFeedback) => void; onSkip: () => void; }) { + const colors = useColors(); const [better, setBetter] = useState(null); const [rating, setRating] = useState(0); const [text, setText] = useState(''); @@ -43,12 +44,12 @@ export function SosFeedbackModal({ keyboardShouldPersistTaps="handled" showsVerticalScrollIndicator={false} > - - Wie war diese Session? - Dein Feedback hilft Lyra besser zu werden. + + Wie war diese Session? + Dein Feedback hilft Lyra besser zu werden. {/* Better Yes/No */} - Fühlst du dich besser? + Fühlst du dich besser? {/* Stars */} - Bewertung + Bewertung {[1, 2, 3, 4, 5].map((n) => ( setRating(n)} hitSlop={6}> @@ -81,9 +82,9 @@ export function SosFeedbackModal({ {/* Comment */} - Bemerkung (optional) + Bemerkung (optional) Überspringen - + Senden @@ -136,6 +137,6 @@ const s = StyleSheet.create({ actions: { flexDirection: 'row', gap: 10, marginTop: 18 }, skipBtn: { flex: 1, paddingVertical: 12, borderRadius: 12, alignItems: 'center', backgroundColor: '#f1f5f9' }, skipTxt: { fontFamily: 'Nunito_700Bold', fontSize: 14, color: '#475569' }, - submitBtn: { flex: 2, paddingVertical: 12, borderRadius: 12, alignItems: 'center', backgroundColor: colors.brandOrange }, + submitBtn: { flex: 2, paddingVertical: 12, borderRadius: 12, alignItems: 'center' }, submitTxt: { fontFamily: 'Nunito_800ExtraBold', fontSize: 14, color: '#fff' }, }); diff --git a/apps/rebreak-native/components/urge/UrgeStats.tsx b/apps/rebreak-native/components/urge/UrgeStats.tsx index 783a51b..0443815 100644 --- a/apps/rebreak-native/components/urge/UrgeStats.tsx +++ b/apps/rebreak-native/components/urge/UrgeStats.tsx @@ -3,7 +3,7 @@ import { View, Text, ActivityIndicator } from 'react-native'; import { Ionicons } from '@expo/vector-icons'; import { useTranslation } from 'react-i18next'; import { apiFetch } from '../../lib/api'; -import { colors } from '../../lib/theme'; +import { useColors } from '../../lib/theme'; type Emotion = 'stress' | 'sadness' | 'anger' | 'empty' | 'boredom' | 'other'; @@ -30,6 +30,7 @@ function emotionLabel(key: string, t: (k: string) => string): string { } function StatCard({ label, value, color }: { label: string; value: string; color: string }) { + const colors = useColors(); return ( {label} @@ -61,6 +62,7 @@ function StatCard({ label, value, color }: { label: string; value: string; color export function UrgeStats() { const { t } = useTranslation(); + const colors = useColors(); const [logs, setLogs] = useState([]); const [loading, setLoading] = useState(true); @@ -175,18 +177,18 @@ export function UrgeStats() { style={{ borderRadius: 18, borderWidth: 1, - borderColor: '#e5e7eb', - backgroundColor: '#fff', + borderColor: colors.border, + backgroundColor: colors.bg, padding: 14, }} > {t('urge.this_week')} - + - + {t('urge.chart_weekday_title')} @@ -254,7 +256,7 @@ export function UrgeStats() { }} /> {day.label} @@ -268,12 +270,12 @@ export function UrgeStats() { style={{ borderRadius: 18, borderWidth: 1, - borderColor: '#e5e7eb', - backgroundColor: '#fff', + borderColor: colors.border, + backgroundColor: colors.bg, padding: 14, }} > - + {t('urge.chart_time_title')} @@ -284,7 +286,7 @@ export function UrgeStats() { style={{ width: 74, fontSize: 12, - color: '#6b7280', + color: colors.textMuted, fontFamily: 'Nunito_600SemiBold', }} > @@ -295,7 +297,7 @@ export function UrgeStats() { flex: 1, height: 7, borderRadius: 4, - backgroundColor: '#e5e7eb', + backgroundColor: colors.surfaceElevated, }} > @@ -328,12 +330,12 @@ export function UrgeStats() { style={{ borderRadius: 18, borderWidth: 1, - borderColor: '#e5e7eb', - backgroundColor: '#fff', + borderColor: colors.border, + backgroundColor: colors.bg, padding: 14, }} > - + {t('urge.chart_top_emotions')} @@ -341,9 +343,9 @@ export function UrgeStats() { {emotionLabel(emo, t)} x{c}