feat(theme): Dark Mode Wave 2 — blocker, mail, chat, community, notifications, all remaining screens
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) <noreply@anthropic.com>
This commit is contained in:
parent
1abd101d53
commit
d7b15e231a
@ -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() {
|
||||
<RefreshControl
|
||||
refreshing={refetchingRooms}
|
||||
onRefresh={refetchRooms}
|
||||
tintColor={colors.brandOrange}
|
||||
tintColor="#007AFF"
|
||||
/>
|
||||
}
|
||||
ListEmptyComponent={
|
||||
loadingRooms ? (
|
||||
<View style={styles.emptyBox}>
|
||||
<ActivityIndicator color={colors.brandOrange} />
|
||||
<ActivityIndicator color="#007AFF" />
|
||||
</View>
|
||||
) : (
|
||||
<View style={styles.emptyBox}>
|
||||
@ -225,13 +229,13 @@ export default function ChatScreen() {
|
||||
<RefreshControl
|
||||
refreshing={refetchingDms}
|
||||
onRefresh={refetchDms}
|
||||
tintColor={colors.brandOrange}
|
||||
tintColor="#007AFF"
|
||||
/>
|
||||
}
|
||||
ListEmptyComponent={
|
||||
loadingDms ? (
|
||||
<View style={styles.emptyBox}>
|
||||
<ActivityIndicator color={colors.brandOrange} />
|
||||
<ActivityIndicator color="#007AFF" />
|
||||
</View>
|
||||
) : (
|
||||
<View style={styles.emptyBox}>
|
||||
@ -257,15 +261,16 @@ export default function ChatScreen() {
|
||||
);
|
||||
}
|
||||
|
||||
const styles = StyleSheet.create({
|
||||
container: { flex: 1, backgroundColor: '#fafafa' },
|
||||
function makeStyles(colors: ReturnType<typeof useColors>) {
|
||||
return StyleSheet.create({
|
||||
container: { flex: 1, backgroundColor: colors.bg },
|
||||
headerSection: {
|
||||
paddingHorizontal: 16,
|
||||
paddingTop: 14,
|
||||
paddingBottom: 10,
|
||||
backgroundColor: '#fff',
|
||||
backgroundColor: colors.bg,
|
||||
borderBottomWidth: StyleSheet.hairlineWidth,
|
||||
borderBottomColor: '#e5e5e5',
|
||||
borderBottomColor: colors.border,
|
||||
},
|
||||
titleRow: {
|
||||
flexDirection: 'row',
|
||||
@ -275,7 +280,7 @@ const styles = StyleSheet.create({
|
||||
title: {
|
||||
fontSize: 22,
|
||||
fontFamily: 'Nunito_800ExtraBold',
|
||||
color: '#171717',
|
||||
color: colors.text,
|
||||
},
|
||||
createBtn: {
|
||||
width: 34,
|
||||
@ -288,7 +293,7 @@ const styles = StyleSheet.create({
|
||||
tabs: {
|
||||
flexDirection: 'row',
|
||||
marginTop: 12,
|
||||
backgroundColor: '#f5f5f5',
|
||||
backgroundColor: colors.surfaceElevated,
|
||||
borderRadius: 10,
|
||||
padding: 3,
|
||||
},
|
||||
@ -301,7 +306,7 @@ const styles = StyleSheet.create({
|
||||
borderRadius: 8,
|
||||
},
|
||||
tabActive: {
|
||||
backgroundColor: '#fff',
|
||||
backgroundColor: colors.surface,
|
||||
shadowColor: '#000',
|
||||
shadowOpacity: 0.05,
|
||||
shadowRadius: 2,
|
||||
@ -310,7 +315,7 @@ const styles = StyleSheet.create({
|
||||
tabText: {
|
||||
fontSize: 12,
|
||||
fontFamily: 'Nunito_600SemiBold',
|
||||
color: '#737373',
|
||||
color: colors.textMuted,
|
||||
marginLeft: 5,
|
||||
},
|
||||
tabTextActive: {
|
||||
@ -341,25 +346,24 @@ const styles = StyleSheet.create({
|
||||
emptyText: {
|
||||
fontSize: 13,
|
||||
fontFamily: 'Nunito_600SemiBold',
|
||||
color: '#a3a3a3',
|
||||
color: colors.textMuted,
|
||||
marginTop: 12,
|
||||
},
|
||||
// DM row styles
|
||||
dmRow: {
|
||||
width: '100%',
|
||||
flexDirection: 'row',
|
||||
alignItems: 'center',
|
||||
paddingHorizontal: 14,
|
||||
paddingVertical: 11,
|
||||
backgroundColor: '#fff',
|
||||
backgroundColor: colors.bg,
|
||||
borderBottomWidth: StyleSheet.hairlineWidth,
|
||||
borderBottomColor: '#f5f5f5',
|
||||
borderBottomColor: colors.border,
|
||||
},
|
||||
dmAvatar: {
|
||||
width: 42,
|
||||
height: 42,
|
||||
borderRadius: 21,
|
||||
backgroundColor: '#e5e5e5',
|
||||
backgroundColor: colors.surfaceElevated,
|
||||
alignItems: 'center',
|
||||
justifyContent: 'center',
|
||||
overflow: 'hidden',
|
||||
@ -369,7 +373,7 @@ const styles = StyleSheet.create({
|
||||
dmAvatarInitials: {
|
||||
fontSize: 13,
|
||||
fontFamily: 'Nunito_700Bold',
|
||||
color: '#525252',
|
||||
color: colors.textMuted,
|
||||
},
|
||||
dmInfo: { flex: 1, minWidth: 0 },
|
||||
dmHeaderRow: {
|
||||
@ -380,7 +384,7 @@ const styles = StyleSheet.create({
|
||||
dmName: {
|
||||
fontSize: 14,
|
||||
fontFamily: 'Nunito_700Bold',
|
||||
color: '#171717',
|
||||
color: colors.text,
|
||||
flexShrink: 1,
|
||||
marginRight: 6,
|
||||
},
|
||||
@ -407,4 +411,5 @@ const styles = StyleSheet.create({
|
||||
fontFamily: 'Nunito_700Bold',
|
||||
color: '#fff',
|
||||
},
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
@ -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 <View style={{ flex: 1, backgroundColor: '#ffffff' }} />;
|
||||
return <View style={{ flex: 1, backgroundColor: colors.bg }} />;
|
||||
}
|
||||
|
||||
@ -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 (
|
||||
<View style={{ flex: 1, backgroundColor: '#fafafa' }}>
|
||||
<View style={{ flex: 1, backgroundColor: colors.bg }}>
|
||||
<AppHeader />
|
||||
<View style={{ flex: 1, alignItems: 'center', justifyContent: 'center' }}>
|
||||
<ActivityIndicator size="large" color="#007AFF" />
|
||||
@ -82,7 +84,7 @@ export default function MailScreen() {
|
||||
}
|
||||
|
||||
return (
|
||||
<View style={{ flex: 1, backgroundColor: '#fafafa' }}>
|
||||
<View style={{ flex: 1, backgroundColor: colors.bg }}>
|
||||
<AppHeader />
|
||||
|
||||
<ScrollView
|
||||
@ -118,7 +120,7 @@ export default function MailScreen() {
|
||||
style={{
|
||||
fontSize: 11,
|
||||
fontFamily: 'Nunito_700Bold',
|
||||
color: '#737373',
|
||||
color: colors.textMuted,
|
||||
textTransform: 'uppercase',
|
||||
letterSpacing: 0.8,
|
||||
}}
|
||||
@ -129,7 +131,7 @@ export default function MailScreen() {
|
||||
style={{
|
||||
fontSize: 11,
|
||||
fontFamily: 'Nunito_400Regular',
|
||||
color: '#a3a3a3',
|
||||
color: colors.textMuted,
|
||||
marginTop: 2,
|
||||
}}
|
||||
>
|
||||
@ -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() {
|
||||
<Ionicons
|
||||
name="add"
|
||||
size={18}
|
||||
color={limitReached ? '#737373' : '#fff'}
|
||||
color={limitReached ? colors.textMuted : '#fff'}
|
||||
style={{ marginRight: 6 }}
|
||||
/>
|
||||
<Text
|
||||
style={{
|
||||
fontSize: 13,
|
||||
fontFamily: 'Nunito_700Bold',
|
||||
color: limitReached ? '#737373' : '#fff',
|
||||
color: limitReached ? colors.textMuted : '#fff',
|
||||
}}
|
||||
>
|
||||
{t('mail.add_account')}
|
||||
|
||||
@ -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 (
|
||||
<SafeAreaView className="flex-1 bg-white" edges={['top']}>
|
||||
<View className="flex-row items-center gap-3 px-5 pt-3 pb-3 border-b border-neutral-200">
|
||||
<SafeAreaView style={{ flex: 1, backgroundColor: colors.bg }} edges={['top']}>
|
||||
<View style={{ flexDirection: 'row', alignItems: 'center', gap: 12, paddingHorizontal: 20, paddingTop: 12, paddingBottom: 12, borderBottomWidth: 1, borderBottomColor: colors.border }}>
|
||||
<Pressable
|
||||
onPress={() => 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' }}
|
||||
>
|
||||
<Ionicons name="arrow-back" size={18} color="#737373" />
|
||||
<Ionicons name="arrow-back" size={18} color={colors.textMuted} />
|
||||
</Pressable>
|
||||
<Text
|
||||
className="text-neutral-900 text-lg flex-1"
|
||||
style={{ fontFamily: 'Nunito_700Bold' }}
|
||||
style={{ color: colors.text, fontSize: 18, flex: 1, fontFamily: 'Nunito_700Bold' }}
|
||||
>
|
||||
{t('notifications.title')}
|
||||
</Text>
|
||||
@ -59,7 +59,7 @@ export default function NotificationsScreen() {
|
||||
<RefreshControl
|
||||
refreshing={!loaded}
|
||||
onRefresh={load}
|
||||
tintColor={colors.brandOrange}
|
||||
tintColor="#007AFF"
|
||||
/>
|
||||
}
|
||||
renderItem={({ item }) => (
|
||||
@ -88,6 +88,7 @@ function NotificationRow({
|
||||
onPress: () => void;
|
||||
onDelete: () => void;
|
||||
}) {
|
||||
const colors = useColors();
|
||||
const isUnread = !notif.readAt;
|
||||
return (
|
||||
<Pressable
|
||||
@ -103,8 +104,8 @@ function NotificationRow({
|
||||
paddingHorizontal: 16,
|
||||
paddingVertical: 12,
|
||||
borderBottomWidth: 1,
|
||||
borderBottomColor: '#f5f5f5',
|
||||
backgroundColor: isUnread ? '#fff7ed' : '#fff',
|
||||
borderBottomColor: colors.border,
|
||||
backgroundColor: isUnread ? colors.surface : colors.bg,
|
||||
}}
|
||||
>
|
||||
{/* Pure-Icon — KEIN bg-Circle (User-Wunsch: kein extra Rand). */}
|
||||
@ -117,7 +118,7 @@ function NotificationRow({
|
||||
</View>
|
||||
<View style={{ flex: 1, minWidth: 0, marginRight: 8 }}>
|
||||
<Text
|
||||
style={{ fontSize: 13, fontFamily: 'Nunito_700Bold', color: '#0a0a0a' }}
|
||||
style={{ fontSize: 13, fontFamily: 'Nunito_700Bold', color: colors.text }}
|
||||
numberOfLines={1}
|
||||
>
|
||||
{notif.actorName}
|
||||
@ -127,7 +128,7 @@ function NotificationRow({
|
||||
style={{
|
||||
fontSize: 12,
|
||||
fontFamily: 'Nunito_400Regular',
|
||||
color: '#525252',
|
||||
color: colors.textMuted,
|
||||
marginTop: 2,
|
||||
}}
|
||||
numberOfLines={2}
|
||||
|
||||
@ -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 (
|
||||
<View style={{ flex: 1, alignItems: 'center', justifyContent: 'center', backgroundColor: '#ffffff' }}>
|
||||
<View style={{ flex: 1, alignItems: 'center', justifyContent: 'center', backgroundColor: colors.bg }}>
|
||||
<ActivityIndicator size="large" color={colors.brandOrange} />
|
||||
</View>
|
||||
);
|
||||
|
||||
@ -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 <View style={{ flex: 1, backgroundColor: '#ffffff' }} />;
|
||||
return <View style={{ flex: 1, backgroundColor: colors.bg }} />;
|
||||
}
|
||||
|
||||
return (
|
||||
<SafeAreaView style={{ flex: 1, backgroundColor: '#ffffff' }} edges={['top']}>
|
||||
<SafeAreaView style={{ flex: 1, backgroundColor: colors.bg }} edges={['top']}>
|
||||
<View
|
||||
style={{
|
||||
paddingHorizontal: 12,
|
||||
@ -48,7 +49,7 @@ export default function DebugScreen() {
|
||||
<Ionicons name="chevron-back" size={26} color={colors.text} />
|
||||
</View>
|
||||
</Pressable>
|
||||
<Text style={{ fontSize: 20, color: '#0a0a0a', fontFamily: 'Nunito_700Bold' }}>
|
||||
<Text style={{ fontSize: 20, color: colors.text, fontFamily: 'Nunito_700Bold' }}>
|
||||
Debug
|
||||
</Text>
|
||||
</View>
|
||||
@ -119,10 +120,11 @@ function DebugStub({
|
||||
subtitle: string;
|
||||
icon: React.ComponentProps<typeof Ionicons>['name'];
|
||||
}) {
|
||||
const colors = useColors();
|
||||
return (
|
||||
<View
|
||||
style={{
|
||||
backgroundColor: '#fafafa',
|
||||
backgroundColor: colors.surface,
|
||||
borderRadius: 14,
|
||||
borderWidth: 1,
|
||||
borderColor: 'rgba(0,0,0,0.05)',
|
||||
@ -139,19 +141,19 @@ function DebugStub({
|
||||
width: 36,
|
||||
height: 36,
|
||||
borderRadius: 11,
|
||||
backgroundColor: '#e5e7eb',
|
||||
backgroundColor: colors.surfaceElevated,
|
||||
alignItems: 'center',
|
||||
justifyContent: 'center',
|
||||
}}
|
||||
>
|
||||
<Ionicons name={icon} size={18} color="#525252" />
|
||||
<Ionicons name={icon} size={18} color={colors.textMuted} />
|
||||
</View>
|
||||
<View style={{ flex: 1 }}>
|
||||
<Text style={{ fontSize: 14, color: '#0a0a0a', fontFamily: 'Nunito_700Bold' }}>{title}</Text>
|
||||
<Text style={{ fontSize: 14, color: colors.text, fontFamily: 'Nunito_700Bold' }}>{title}</Text>
|
||||
<Text
|
||||
style={{
|
||||
fontSize: 12,
|
||||
color: '#737373',
|
||||
color: colors.textMuted,
|
||||
fontFamily: 'Nunito_400Regular',
|
||||
marginTop: 3,
|
||||
lineHeight: 17,
|
||||
|
||||
@ -20,7 +20,7 @@ import { supabase } from '../lib/supabase';
|
||||
import { ChatBubble, type ChatMsg } from '../components/chat/ChatBubble';
|
||||
import { ChatInput, type SendPayload } from '../components/chat/ChatInput';
|
||||
import { useDmRealtime } from '../hooks/useChatRealtime';
|
||||
import { colors } from '../lib/theme';
|
||||
import { useColors } from '../lib/theme';
|
||||
|
||||
type DmHistoryResponse = {
|
||||
partner: {
|
||||
@ -52,6 +52,8 @@ export default function DmScreen() {
|
||||
const { t } = useTranslation();
|
||||
const router = useRouter();
|
||||
const insets = useSafeAreaInsets();
|
||||
const colors = useColors();
|
||||
const styles = makeStyles(colors);
|
||||
const queryClient = useQueryClient();
|
||||
const flatRef = useRef<FlatList>(null);
|
||||
const [myUserId, setMyUserId] = useState<string | undefined>(undefined);
|
||||
@ -234,7 +236,7 @@ export default function DmScreen() {
|
||||
{/* Header */}
|
||||
<View style={styles.header}>
|
||||
<Pressable style={styles.backBtn} onPress={() => router.back()} hitSlop={8}>
|
||||
<Ionicons name="chevron-back" size={22} color="#0a0a0a" />
|
||||
<Ionicons name="chevron-back" size={22} color={colors.text} />
|
||||
</Pressable>
|
||||
<View style={styles.headerCenter}>
|
||||
<View style={styles.headerAvatar}>
|
||||
@ -260,7 +262,7 @@ export default function DmScreen() {
|
||||
>
|
||||
{isLoading && messages.length === 0 ? (
|
||||
<View style={styles.loadingBox}>
|
||||
<ActivityIndicator color={colors.brandOrange} />
|
||||
<ActivityIndicator color="#007AFF" />
|
||||
</View>
|
||||
) : messages.length === 0 ? (
|
||||
<View style={styles.loadingBox}>
|
||||
@ -302,23 +304,24 @@ export default function DmScreen() {
|
||||
);
|
||||
}
|
||||
|
||||
const styles = StyleSheet.create({
|
||||
container: { flex: 1, backgroundColor: '#fafafa' },
|
||||
function makeStyles(colors: ReturnType<typeof useColors>) {
|
||||
return StyleSheet.create({
|
||||
container: { flex: 1, backgroundColor: colors.bg },
|
||||
header: {
|
||||
flexDirection: 'row',
|
||||
alignItems: 'center',
|
||||
justifyContent: 'space-between',
|
||||
paddingHorizontal: 12,
|
||||
paddingVertical: 10,
|
||||
backgroundColor: '#fff',
|
||||
backgroundColor: colors.bg,
|
||||
borderBottomWidth: StyleSheet.hairlineWidth,
|
||||
borderBottomColor: '#e5e5e5',
|
||||
borderBottomColor: colors.border,
|
||||
},
|
||||
backBtn: {
|
||||
width: 36,
|
||||
height: 36,
|
||||
borderRadius: 12,
|
||||
backgroundColor: '#f5f5f5',
|
||||
backgroundColor: colors.surfaceElevated,
|
||||
alignItems: 'center',
|
||||
justifyContent: 'center',
|
||||
},
|
||||
@ -333,7 +336,7 @@ const styles = StyleSheet.create({
|
||||
width: 32,
|
||||
height: 32,
|
||||
borderRadius: 16,
|
||||
backgroundColor: '#e5e5e5',
|
||||
backgroundColor: colors.surfaceElevated,
|
||||
alignItems: 'center',
|
||||
justifyContent: 'center',
|
||||
overflow: 'hidden',
|
||||
@ -343,12 +346,12 @@ const styles = StyleSheet.create({
|
||||
headerAvatarInitials: {
|
||||
fontSize: 11,
|
||||
fontFamily: 'Nunito_700Bold',
|
||||
color: '#737373',
|
||||
color: colors.textMuted,
|
||||
},
|
||||
headerName: {
|
||||
fontSize: 15,
|
||||
fontFamily: 'Nunito_700Bold',
|
||||
color: '#171717',
|
||||
color: colors.text,
|
||||
flexShrink: 1,
|
||||
},
|
||||
loadingBox: {
|
||||
@ -359,7 +362,8 @@ const styles = StyleSheet.create({
|
||||
emptyText: {
|
||||
fontSize: 13,
|
||||
fontFamily: 'Nunito_600SemiBold',
|
||||
color: '#a3a3a3',
|
||||
color: colors.textMuted,
|
||||
marginTop: 12,
|
||||
},
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
@ -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<GameType | null>(null);
|
||||
const [lastScore, setLastScore] = useState<LastScore>(null);
|
||||
const [gameStats, setGameStats] = useState<GameStats>(EMPTY_STATS);
|
||||
@ -70,7 +71,7 @@ export default function GamesScreen() {
|
||||
|
||||
if (active) {
|
||||
return (
|
||||
<SafeAreaView style={{ flex: 1, backgroundColor: '#ffffff' }} edges={['top']}>
|
||||
<SafeAreaView style={{ flex: 1, backgroundColor: colors.bg }} edges={['top']}>
|
||||
<View
|
||||
style={{
|
||||
paddingHorizontal: 12,
|
||||
@ -79,7 +80,7 @@ export default function GamesScreen() {
|
||||
alignItems: 'center',
|
||||
justifyContent: 'space-between',
|
||||
borderBottomWidth: 1,
|
||||
borderBottomColor: 'rgba(0,0,0,0.06)',
|
||||
borderBottomColor: colors.border,
|
||||
}}
|
||||
>
|
||||
<Pressable
|
||||
@ -102,7 +103,7 @@ export default function GamesScreen() {
|
||||
</Text>
|
||||
</View>
|
||||
</Pressable>
|
||||
<Text style={{ fontSize: 14, fontFamily: 'Nunito_700Bold', color: '#0a0a0a' }}>
|
||||
<Text style={{ fontSize: 14, fontFamily: 'Nunito_700Bold', color: colors.text }}>
|
||||
{t(GAME_META.find((g) => g.id === active)!.titleKey)}
|
||||
</Text>
|
||||
<View style={{ width: 60 }} />
|
||||
@ -127,7 +128,7 @@ export default function GamesScreen() {
|
||||
}
|
||||
|
||||
return (
|
||||
<SafeAreaView style={{ flex: 1, backgroundColor: '#ffffff' }} edges={['top']}>
|
||||
<SafeAreaView style={{ flex: 1, backgroundColor: colors.bg }} edges={['top']}>
|
||||
<View
|
||||
style={{
|
||||
paddingHorizontal: 12,
|
||||
@ -137,7 +138,7 @@ export default function GamesScreen() {
|
||||
alignItems: 'center',
|
||||
gap: 8,
|
||||
borderBottomWidth: 1,
|
||||
borderBottomColor: 'rgba(0,0,0,0.06)',
|
||||
borderBottomColor: colors.border,
|
||||
}}
|
||||
>
|
||||
<Pressable
|
||||
@ -156,7 +157,7 @@ export default function GamesScreen() {
|
||||
<Ionicons name="chevron-back" size={26} color={colors.text} />
|
||||
</View>
|
||||
</Pressable>
|
||||
<Text style={{ fontSize: 20, color: '#0a0a0a', fontFamily: 'Nunito_700Bold' }}>
|
||||
<Text style={{ fontSize: 20, color: colors.text, fontFamily: 'Nunito_700Bold' }}>
|
||||
{t('games.title')}
|
||||
</Text>
|
||||
</View>
|
||||
@ -169,7 +170,7 @@ export default function GamesScreen() {
|
||||
<Text
|
||||
style={{
|
||||
fontSize: 13,
|
||||
color: '#737373',
|
||||
color: colors.textMuted,
|
||||
fontFamily: 'Nunito_400Regular',
|
||||
lineHeight: 19,
|
||||
marginBottom: 18,
|
||||
@ -232,7 +233,7 @@ export default function GamesScreen() {
|
||||
style={{
|
||||
textAlign: 'center',
|
||||
fontSize: 11,
|
||||
color: '#a3a3a3',
|
||||
color: colors.textMuted,
|
||||
fontFamily: 'Nunito_400Regular',
|
||||
marginTop: 24,
|
||||
opacity: 0.7,
|
||||
|
||||
@ -3,7 +3,7 @@ import { View, Text, ScrollView, Pressable, Image } from 'react-native';
|
||||
import { useSafeAreaInsets } from 'react-native-safe-area-context';
|
||||
import { useLocalSearchParams, useRouter } from 'expo-router';
|
||||
import { Ionicons } from '@expo/vector-icons';
|
||||
import { colors } from '../../lib/theme';
|
||||
import { useColors } from '../../lib/theme';
|
||||
import { resolveAvatar } from '../../lib/resolveAvatar';
|
||||
import type { Plan } from '../../hooks/useUserPlan';
|
||||
|
||||
@ -52,13 +52,14 @@ type StatProps = {
|
||||
};
|
||||
|
||||
function ForeignStat({ value, label }: StatProps) {
|
||||
const colors = useColors();
|
||||
return (
|
||||
<View
|
||||
style={{
|
||||
flex: 1,
|
||||
backgroundColor: '#ffffff',
|
||||
backgroundColor: colors.bg,
|
||||
borderWidth: 1,
|
||||
borderColor: '#e5e5e5',
|
||||
borderColor: colors.border,
|
||||
borderRadius: 14,
|
||||
paddingVertical: 14,
|
||||
alignItems: 'center',
|
||||
@ -85,6 +86,7 @@ function ForeignStat({ value, label }: StatProps) {
|
||||
export default function ForeignProfileScreen() {
|
||||
const insets = useSafeAreaInsets();
|
||||
const router = useRouter();
|
||||
const colors = useColors();
|
||||
const { userId } = useLocalSearchParams<{ userId: string }>();
|
||||
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 (
|
||||
<View style={{ flex: 1, backgroundColor: '#ffffff' }}>
|
||||
<View style={{ flex: 1, backgroundColor: colors.bg }}>
|
||||
<View
|
||||
style={{
|
||||
paddingTop: insets.top,
|
||||
backgroundColor: '#ffffff',
|
||||
backgroundColor: colors.bg,
|
||||
borderBottomWidth: 1,
|
||||
borderBottomColor: '#e5e5e5',
|
||||
borderBottomColor: colors.border,
|
||||
}}
|
||||
>
|
||||
<View
|
||||
@ -147,7 +149,7 @@ export default function ForeignProfileScreen() {
|
||||
alignItems: 'center',
|
||||
justifyContent: 'center',
|
||||
overflow: 'hidden',
|
||||
backgroundColor: showImage ? '#fafafa' : colors.brandOrange,
|
||||
backgroundColor: showImage ? colors.surface : colors.brandOrange,
|
||||
}}
|
||||
>
|
||||
{showImage ? (
|
||||
@ -215,9 +217,9 @@ export default function ForeignProfileScreen() {
|
||||
<View style={{
|
||||
paddingVertical: 11,
|
||||
borderRadius: 12,
|
||||
backgroundColor: isFollowing ? '#f5f5f5' : colors.brandOrange,
|
||||
backgroundColor: isFollowing ? colors.surfaceElevated : colors.brandOrange,
|
||||
borderWidth: 1,
|
||||
borderColor: isFollowing ? '#e5e5e5' : colors.brandOrange,
|
||||
borderColor: isFollowing ? colors.border : colors.brandOrange,
|
||||
alignItems: 'center',
|
||||
}}>
|
||||
<Text
|
||||
@ -244,9 +246,9 @@ export default function ForeignProfileScreen() {
|
||||
<View style={{
|
||||
paddingVertical: 11,
|
||||
borderRadius: 12,
|
||||
backgroundColor: '#ffffff',
|
||||
backgroundColor: colors.bg,
|
||||
borderWidth: 1,
|
||||
borderColor: '#e5e5e5',
|
||||
borderColor: colors.border,
|
||||
alignItems: 'center',
|
||||
}}>
|
||||
<Text
|
||||
@ -292,9 +294,9 @@ export default function ForeignProfileScreen() {
|
||||
</Text>
|
||||
<View
|
||||
style={{
|
||||
backgroundColor: '#ffffff',
|
||||
backgroundColor: colors.bg,
|
||||
borderWidth: 1,
|
||||
borderColor: '#e5e5e5',
|
||||
borderColor: colors.border,
|
||||
borderRadius: 14,
|
||||
padding: 16,
|
||||
alignItems: 'center',
|
||||
|
||||
@ -18,28 +18,29 @@ import * as ImagePicker from 'expo-image-picker';
|
||||
// TODO(sdk54): migrate to new expo-file-system class-based API — see Task #14
|
||||
import * as FileSystem from 'expo-file-system/legacy';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
import { colors } from '../../lib/theme';
|
||||
import { useColors } from '../../lib/theme';
|
||||
import { HERO_AVATARS, getAvatarUrl } from '../../lib/avatars';
|
||||
import { resolveAvatar } from '../../lib/resolveAvatar';
|
||||
import { apiFetch } from '../../lib/api';
|
||||
import { useMe } from '../../hooks/useMe';
|
||||
|
||||
const INPUT_STYLE = {
|
||||
export default function ProfileEditScreen() {
|
||||
const router = useRouter();
|
||||
const insets = useSafeAreaInsets();
|
||||
const { t } = useTranslation();
|
||||
const colors = useColors();
|
||||
const { me, reload } = useMe();
|
||||
|
||||
const INPUT_STYLE = {
|
||||
fontSize: 16,
|
||||
lineHeight: 22,
|
||||
paddingVertical: 14,
|
||||
paddingHorizontal: 16,
|
||||
color: colors.text,
|
||||
fontFamily: 'Nunito_400Regular',
|
||||
backgroundColor: '#f5f5f5',
|
||||
backgroundColor: colors.surfaceElevated,
|
||||
borderRadius: 12,
|
||||
} as const;
|
||||
|
||||
export default function ProfileEditScreen() {
|
||||
const router = useRouter();
|
||||
const insets = useSafeAreaInsets();
|
||||
const { t } = useTranslation();
|
||||
const { me, reload } = useMe();
|
||||
} as const;
|
||||
|
||||
const [nickname, setNickname] = useState(me?.nickname ?? '');
|
||||
const [avatarId, setAvatarId] = useState<string | null>(me?.avatar ?? null);
|
||||
|
||||
@ -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<FlatList>(null);
|
||||
const [myUserId, setMyUserId] = useState<string | undefined>();
|
||||
@ -298,7 +300,7 @@ export default function RoomScreen() {
|
||||
{/* Header */}
|
||||
<View style={styles.header}>
|
||||
<Pressable style={styles.iconBtn} onPress={() => router.back()} hitSlop={8}>
|
||||
<Ionicons name="chevron-back" size={22} color="#0a0a0a" />
|
||||
<Ionicons name="chevron-back" size={22} color={colors.text} />
|
||||
</Pressable>
|
||||
<View style={styles.headerCenter}>
|
||||
<View style={styles.headerAvatar}>
|
||||
@ -320,7 +322,7 @@ export default function RoomScreen() {
|
||||
</View>
|
||||
</View>
|
||||
<Pressable style={styles.iconBtn} onPress={() => setSettingsOpen(true)} hitSlop={8}>
|
||||
<Ionicons name="ellipsis-horizontal" size={20} color="#0a0a0a" />
|
||||
<Ionicons name="ellipsis-horizontal" size={20} color={colors.text} />
|
||||
</Pressable>
|
||||
</View>
|
||||
|
||||
@ -430,6 +432,8 @@ function RoomSettingsModal({
|
||||
roomId: string;
|
||||
}) {
|
||||
const { t } = useTranslation();
|
||||
const colors = useColors();
|
||||
const modal = makeModalStyles(colors);
|
||||
const [pendingRequests, setPendingRequests] = useState<any[]>([]);
|
||||
const [loadingReqs, setLoadingReqs] = useState(false);
|
||||
|
||||
@ -637,22 +641,23 @@ function RoomSettingsModal({
|
||||
);
|
||||
}
|
||||
|
||||
const styles = StyleSheet.create({
|
||||
container: { flex: 1, backgroundColor: '#fafafa' },
|
||||
function makeStyles(colors: ReturnType<typeof useColors>) {
|
||||
return StyleSheet.create({
|
||||
container: { flex: 1, backgroundColor: colors.bg },
|
||||
header: {
|
||||
flexDirection: 'row',
|
||||
alignItems: 'center',
|
||||
paddingHorizontal: 12,
|
||||
paddingVertical: 10,
|
||||
backgroundColor: '#fff',
|
||||
backgroundColor: colors.bg,
|
||||
borderBottomWidth: StyleSheet.hairlineWidth,
|
||||
borderBottomColor: '#e5e5e5',
|
||||
borderBottomColor: colors.border,
|
||||
},
|
||||
iconBtn: {
|
||||
width: 36,
|
||||
height: 36,
|
||||
borderRadius: 12,
|
||||
backgroundColor: '#f5f5f5',
|
||||
backgroundColor: colors.surfaceElevated,
|
||||
alignItems: 'center',
|
||||
justifyContent: 'center',
|
||||
},
|
||||
@ -666,7 +671,7 @@ const styles = StyleSheet.create({
|
||||
width: 36,
|
||||
height: 36,
|
||||
borderRadius: 18,
|
||||
backgroundColor: '#e5e5e5',
|
||||
backgroundColor: colors.surfaceElevated,
|
||||
alignItems: 'center',
|
||||
justifyContent: 'center',
|
||||
overflow: 'hidden',
|
||||
@ -676,17 +681,17 @@ const styles = StyleSheet.create({
|
||||
headerAvatarInitials: {
|
||||
fontSize: 12,
|
||||
fontFamily: 'Nunito_700Bold',
|
||||
color: '#737373',
|
||||
color: colors.textMuted,
|
||||
},
|
||||
headerName: {
|
||||
fontSize: 15,
|
||||
fontFamily: 'Nunito_700Bold',
|
||||
color: '#171717',
|
||||
color: colors.text,
|
||||
},
|
||||
headerSub: {
|
||||
fontSize: 11,
|
||||
fontFamily: 'Nunito_500Medium',
|
||||
color: '#737373',
|
||||
color: colors.textMuted,
|
||||
marginTop: 1,
|
||||
},
|
||||
loadingBox: { flex: 1, alignItems: 'center', justifyContent: 'center' },
|
||||
@ -699,20 +704,20 @@ const styles = StyleSheet.create({
|
||||
joinTitle: {
|
||||
fontSize: 20,
|
||||
fontFamily: 'Nunito_700Bold',
|
||||
color: '#171717',
|
||||
color: colors.text,
|
||||
marginTop: 14,
|
||||
},
|
||||
joinDesc: {
|
||||
fontSize: 13,
|
||||
fontFamily: 'Nunito_500Medium',
|
||||
color: '#737373',
|
||||
color: colors.textMuted,
|
||||
marginTop: 6,
|
||||
textAlign: 'center',
|
||||
},
|
||||
joinHint: {
|
||||
fontSize: 12,
|
||||
fontFamily: 'Nunito_500Medium',
|
||||
color: '#a3a3a3',
|
||||
color: colors.textMuted,
|
||||
marginTop: 18,
|
||||
textAlign: 'center',
|
||||
},
|
||||
@ -745,23 +750,25 @@ const styles = StyleSheet.create({
|
||||
fontFamily: 'Nunito_700Bold',
|
||||
marginLeft: 6,
|
||||
},
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
const modal = StyleSheet.create({
|
||||
container: { flex: 1, backgroundColor: '#fafafa' },
|
||||
function makeModalStyles(colors: ReturnType<typeof useColors>) {
|
||||
return StyleSheet.create({
|
||||
container: { flex: 1, backgroundColor: colors.bg },
|
||||
header: {
|
||||
flexDirection: 'row',
|
||||
alignItems: 'center',
|
||||
justifyContent: 'space-between',
|
||||
paddingHorizontal: 16,
|
||||
paddingVertical: 12,
|
||||
backgroundColor: '#fff',
|
||||
backgroundColor: colors.bg,
|
||||
borderBottomWidth: StyleSheet.hairlineWidth,
|
||||
borderBottomColor: '#e5e5e5',
|
||||
borderBottomColor: colors.border,
|
||||
},
|
||||
title: { fontSize: 16, fontFamily: 'Nunito_700Bold', color: '#171717' },
|
||||
title: { fontSize: 16, fontFamily: 'Nunito_700Bold', color: colors.text },
|
||||
section: {
|
||||
backgroundColor: '#fff',
|
||||
backgroundColor: colors.surface,
|
||||
borderRadius: 12,
|
||||
padding: 14,
|
||||
marginBottom: 12,
|
||||
@ -769,7 +776,7 @@ const modal = StyleSheet.create({
|
||||
sectionTitle: {
|
||||
fontSize: 12,
|
||||
fontFamily: 'Nunito_700Bold',
|
||||
color: '#737373',
|
||||
color: colors.textMuted,
|
||||
textTransform: 'uppercase',
|
||||
marginBottom: 10,
|
||||
letterSpacing: 0.5,
|
||||
@ -777,7 +784,7 @@ const modal = StyleSheet.create({
|
||||
avatarWrap: { alignSelf: 'center', marginBottom: 10 },
|
||||
avatar: { width: 80, height: 80, borderRadius: 40 },
|
||||
avatarPlaceholder: {
|
||||
backgroundColor: '#e5e5e5',
|
||||
backgroundColor: colors.surfaceElevated,
|
||||
alignItems: 'center',
|
||||
justifyContent: 'center',
|
||||
},
|
||||
@ -790,20 +797,20 @@ const modal = StyleSheet.create({
|
||||
borderRadius: 14,
|
||||
backgroundColor: '#007AFF',
|
||||
borderWidth: 3,
|
||||
borderColor: '#fff',
|
||||
borderColor: colors.bg,
|
||||
alignItems: 'center',
|
||||
justifyContent: 'center',
|
||||
},
|
||||
roomName: {
|
||||
fontSize: 17,
|
||||
fontFamily: 'Nunito_700Bold',
|
||||
color: '#171717',
|
||||
color: colors.text,
|
||||
textAlign: 'center',
|
||||
},
|
||||
roomDesc: {
|
||||
fontSize: 12,
|
||||
fontFamily: 'Nunito_500Medium',
|
||||
color: '#737373',
|
||||
color: colors.textMuted,
|
||||
textAlign: 'center',
|
||||
marginTop: 4,
|
||||
},
|
||||
@ -812,13 +819,13 @@ const modal = StyleSheet.create({
|
||||
alignItems: 'center',
|
||||
paddingVertical: 8,
|
||||
borderBottomWidth: StyleSheet.hairlineWidth,
|
||||
borderBottomColor: '#f5f5f5',
|
||||
borderBottomColor: colors.border,
|
||||
},
|
||||
memberAvatar: {
|
||||
width: 32,
|
||||
height: 32,
|
||||
borderRadius: 16,
|
||||
backgroundColor: '#e5e5e5',
|
||||
backgroundColor: colors.surfaceElevated,
|
||||
alignItems: 'center',
|
||||
justifyContent: 'center',
|
||||
overflow: 'hidden',
|
||||
@ -828,17 +835,17 @@ const modal = StyleSheet.create({
|
||||
memberInitials: {
|
||||
fontSize: 11,
|
||||
fontFamily: 'Nunito_700Bold',
|
||||
color: '#737373',
|
||||
color: colors.textMuted,
|
||||
},
|
||||
memberName: { fontSize: 13, fontFamily: 'Nunito_600SemiBold', color: '#171717' },
|
||||
memberRole: { fontSize: 11, color: '#a3a3a3', marginTop: 1, textTransform: 'capitalize' },
|
||||
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: '#a3a3a3' },
|
||||
emptyText: { fontSize: 12, color: colors.textMuted },
|
||||
leaveBtn: {
|
||||
flexDirection: 'row',
|
||||
alignItems: 'center',
|
||||
@ -854,4 +861,5 @@ const modal = StyleSheet.create({
|
||||
fontFamily: 'Nunito_700Bold',
|
||||
marginLeft: 6,
|
||||
},
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
@ -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<FlatList>(null);
|
||||
|
||||
const [messages, setMessages] = useState<SosMsg[]>([]);
|
||||
@ -1089,7 +1091,7 @@ export default function SOSScreen() {
|
||||
{/* Header */}
|
||||
<View style={[st.topBar, { top: insets.top + 6 }]}>
|
||||
<Pressable style={st.actionBtn} onPress={attemptExit} hitSlop={12}>
|
||||
<Ionicons name="close" size={22} color="#374151" />
|
||||
<Ionicons name="close" size={22} color={colors.textMuted} />
|
||||
</Pressable>
|
||||
<View style={st.avatarCenter}>
|
||||
<RiveAvatar emotion={emotion} size="md" />
|
||||
@ -1286,23 +1288,24 @@ export default function SOSScreen() {
|
||||
);
|
||||
}
|
||||
|
||||
const st = StyleSheet.create({
|
||||
container: { flex: 1, backgroundColor: '#ffffff' },
|
||||
function makeStyles(colors: ReturnType<typeof useColors>) {
|
||||
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: '#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 },
|
||||
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: '#0a0a0a' },
|
||||
avatarName: { fontSize: 14, fontFamily: 'Nunito_700Bold', color: colors.text },
|
||||
speakingRow: { flexDirection: 'row', alignItems: 'center', gap: 6 },
|
||||
stopBtn: { width: 18, height: 18, borderRadius: 9, backgroundColor: '#f5f5f5', alignItems: 'center', justifyContent: 'center' },
|
||||
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: '#9ca3af', // sichtbarer Ring (medium-grau gegen weiß)
|
||||
backgroundColor: '#ffffff',
|
||||
borderColor: colors.border,
|
||||
backgroundColor: colors.bg,
|
||||
paddingHorizontal: 16,
|
||||
paddingVertical: 11,
|
||||
shadowColor: '#000',
|
||||
@ -1312,13 +1315,14 @@ const st = StyleSheet.create({
|
||||
elevation: 3,
|
||||
},
|
||||
chipPressed: {
|
||||
backgroundColor: '#f3f4f6',
|
||||
borderColor: '#6b7280', // dunkler beim Press → spürbares Feedback
|
||||
backgroundColor: colors.surfaceElevated,
|
||||
borderColor: colors.textMuted,
|
||||
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' },
|
||||
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' },
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
@ -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<TextInput>(null);
|
||||
@ -101,7 +102,7 @@ export function ComposeCard({ onPosted }: Props) {
|
||||
const showActions = focused || content.length > 0;
|
||||
|
||||
return (
|
||||
<View className="bg-white border border-neutral-200 rounded-2xl p-4 mb-4">
|
||||
<View style={{ backgroundColor: colors.bg, borderWidth: 1, borderColor: colors.border, borderRadius: 16, padding: 16, marginBottom: 16 }}>
|
||||
<View className="flex-row items-start gap-3">
|
||||
<Image
|
||||
source={{ uri: avatarUrl }}
|
||||
@ -114,10 +115,10 @@ export function ComposeCard({ onPosted }: Props) {
|
||||
onChangeText={setContent}
|
||||
onFocus={() => 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 && (
|
||||
<View className="relative mt-2">
|
||||
|
||||
@ -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 (
|
||||
<View
|
||||
@ -119,6 +120,7 @@ function DeviceLimitRow({
|
||||
|
||||
export function DeviceLimitReachedSheet() {
|
||||
const { t } = useTranslation();
|
||||
const colors = useColors();
|
||||
const sheetRef = useRef<TrueSheet>(null);
|
||||
const { visible, devices, max, plan, hide, removeDevice } = useDeviceLimitStore();
|
||||
const [removingId, setRemovingId] = useState<string | null>(null);
|
||||
@ -195,7 +197,7 @@ export function DeviceLimitReachedSheet() {
|
||||
|
||||
<View
|
||||
style={{
|
||||
backgroundColor: '#ffffff',
|
||||
backgroundColor: colors.bg,
|
||||
borderRadius: 14,
|
||||
overflow: 'hidden',
|
||||
borderWidth: 1,
|
||||
|
||||
@ -6,6 +6,7 @@ import { useTranslation } from 'react-i18next';
|
||||
import { useNotificationStore, type AppNotification } from '../stores/notifications';
|
||||
import { resolveAvatar } from '../lib/resolveAvatar';
|
||||
import { HeroShieldCheck } from './HeroShieldCheck';
|
||||
import { useColors } from '../lib/theme';
|
||||
|
||||
type Props = {
|
||||
visible: boolean;
|
||||
@ -16,6 +17,7 @@ type Props = {
|
||||
|
||||
export function NotificationsDropdown({ visible, onClose, topOffset }: Props) {
|
||||
const { t } = useTranslation();
|
||||
const colors = useColors();
|
||||
const router = useRouter();
|
||||
const items = useNotificationStore((s) => 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,
|
||||
}}
|
||||
>
|
||||
<Text
|
||||
style={{ flex: 1, fontSize: 14, fontFamily: 'Nunito_700Bold', color: '#0a0a0a' }}
|
||||
style={{ flex: 1, fontSize: 14, fontFamily: 'Nunito_700Bold', color: colors.text }}
|
||||
>
|
||||
{t('notifications.title')}
|
||||
</Text>
|
||||
@ -114,13 +116,13 @@ export function NotificationsDropdown({ visible, onClose, topOffset }: Props) {
|
||||
|
||||
{items.length === 0 ? (
|
||||
<View style={{ paddingVertical: 32, alignItems: 'center' }}>
|
||||
<Ionicons name="notifications-off-outline" size={28} color="#a3a3a3" />
|
||||
<Ionicons name="notifications-off-outline" size={28} color={colors.textMuted} />
|
||||
<Text
|
||||
style={{
|
||||
marginTop: 8,
|
||||
fontSize: 13,
|
||||
fontFamily: 'Nunito_600SemiBold',
|
||||
color: '#525252',
|
||||
color: colors.textMuted,
|
||||
}}
|
||||
>
|
||||
{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,
|
||||
}}
|
||||
>
|
||||
|
||||
@ -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<T> = {
|
||||
value: T;
|
||||
@ -51,6 +51,7 @@ export function OptionsBottomSheet<T extends string | number>({
|
||||
onClose,
|
||||
}: Props<T>) {
|
||||
const insets = useSafeAreaInsets();
|
||||
const colors = useColors();
|
||||
const translateY = useRef(new Animated.Value(400)).current;
|
||||
const backdropOpacity = useRef(new Animated.Value(0)).current;
|
||||
|
||||
|
||||
@ -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 (
|
||||
<View className="bg-white border border-neutral-200 rounded-2xl p-3 mb-3">
|
||||
<View style={{ backgroundColor: colors.bg, borderWidth: 1, borderColor: colors.border, borderRadius: 16, padding: 12, marginBottom: 12 }}>
|
||||
{/* Repost header */}
|
||||
{post.repostOf && (
|
||||
<View className="flex-row items-center gap-1.5 mb-3">
|
||||
@ -194,35 +196,35 @@ function PostCardImpl({ post, onCommentPress }: Props) {
|
||||
</View>
|
||||
)}
|
||||
<View className="flex-1 min-w-0">
|
||||
<Text className="text-sm text-neutral-900" numberOfLines={1} style={{ fontFamily: 'Nunito_600SemiBold' }}>
|
||||
<Text style={{ fontSize: 14, color: colors.text, fontFamily: 'Nunito_600SemiBold' }} numberOfLines={1}>
|
||||
{authorLabel}
|
||||
</Text>
|
||||
{authorDescription !== undefined && (
|
||||
<Text className="text-xs text-neutral-400" style={{ fontFamily: 'Nunito_400Regular' }}>{authorDescription}</Text>
|
||||
<Text style={{ fontSize: 12, color: colors.textMuted, fontFamily: 'Nunito_400Regular' }}>{authorDescription}</Text>
|
||||
)}
|
||||
</View>
|
||||
</View>
|
||||
<Text className="text-xs text-neutral-400 shrink-0 ml-2 mt-0.5" style={{ fontFamily: 'Nunito_400Regular' }}>
|
||||
<Text style={{ fontSize: 12, color: colors.textMuted, fontFamily: 'Nunito_400Regular', flexShrink: 0, marginLeft: 8, marginTop: 2 }}>
|
||||
{formatRelativeTime(post.createdAt)}
|
||||
</Text>
|
||||
</View>
|
||||
|
||||
{/* Content — hidden for domain_vote (replaced by poll below) */}
|
||||
{!!displayContent && post.category !== 'domain_vote' && (
|
||||
<Text className="text-sm text-neutral-800 leading-relaxed mb-0" style={{ fontFamily: 'Nunito_400Regular' }}>
|
||||
<Text style={{ fontSize: 14, color: colors.textMuted, fontFamily: 'Nunito_400Regular', lineHeight: 21 }}>
|
||||
{displayContent}
|
||||
</Text>
|
||||
)}
|
||||
|
||||
{/* domain_approved: favicon + domain name + shield badge */}
|
||||
{post.category === 'domain_approved' && !!approvedDomain && (
|
||||
<View className="flex-row items-center gap-2.5 mt-3 rounded-xl border border-neutral-200 bg-neutral-50 px-3 py-2">
|
||||
<View style={{ flexDirection: 'row', alignItems: 'center', gap: 10, marginTop: 12, borderRadius: 12, borderWidth: 1, borderColor: colors.border, backgroundColor: colors.surface, paddingHorizontal: 12, paddingVertical: 8 }}>
|
||||
<DomainFavicon domain={approvedDomain} size={24} />
|
||||
<View className="flex-1 min-w-0">
|
||||
<Text className="text-xs font-semibold text-neutral-900 truncate" style={{ fontFamily: 'Nunito_700Bold' }}>
|
||||
<View style={{ flex: 1, minWidth: 0 }}>
|
||||
<Text style={{ fontSize: 12, color: colors.text, fontFamily: 'Nunito_700Bold' }} numberOfLines={1}>
|
||||
{approvedDomain}
|
||||
</Text>
|
||||
<Text className="text-xs text-neutral-400" style={{ fontFamily: 'Nunito_400Regular' }}>
|
||||
<Text style={{ fontSize: 12, color: colors.textMuted, fontFamily: 'Nunito_400Regular' }}>
|
||||
{t('community.domain_added_to_blocklist')}
|
||||
</Text>
|
||||
</View>
|
||||
|
||||
@ -1,18 +1,20 @@
|
||||
import { View } from 'react-native';
|
||||
import { useColors } from '../lib/theme';
|
||||
|
||||
export function PostCardSkeleton() {
|
||||
const colors = useColors();
|
||||
return (
|
||||
<View className="bg-white border border-neutral-200 rounded-2xl p-4 mb-3">
|
||||
<View className="flex-row items-center gap-3 mb-3">
|
||||
<View className="w-9 h-9 rounded-full bg-neutral-200" />
|
||||
<View className="flex-1 gap-1.5">
|
||||
<View className="h-3 bg-neutral-200 rounded w-1/3" />
|
||||
<View className="h-2.5 bg-neutral-100 rounded w-1/4" />
|
||||
<View style={{ backgroundColor: colors.bg, borderWidth: 1, borderColor: colors.border, borderRadius: 16, padding: 16, marginBottom: 12 }}>
|
||||
<View style={{ flexDirection: 'row', alignItems: 'center', gap: 12, marginBottom: 12 }}>
|
||||
<View style={{ width: 36, height: 36, borderRadius: 18, backgroundColor: colors.surfaceElevated }} />
|
||||
<View style={{ flex: 1, gap: 6 }}>
|
||||
<View style={{ height: 12, backgroundColor: colors.surfaceElevated, borderRadius: 6, width: '33%' }} />
|
||||
<View style={{ height: 10, backgroundColor: colors.surface, borderRadius: 6, width: '25%' }} />
|
||||
</View>
|
||||
</View>
|
||||
<View className="h-3 bg-neutral-200 rounded w-full mb-2" />
|
||||
<View className="h-3 bg-neutral-200 rounded w-3/4 mb-2" />
|
||||
<View className="h-3 bg-neutral-100 rounded w-1/2" />
|
||||
<View style={{ height: 12, backgroundColor: colors.surfaceElevated, borderRadius: 6, width: '100%', marginBottom: 8 }} />
|
||||
<View style={{ height: 12, backgroundColor: colors.surfaceElevated, borderRadius: 6, width: '75%', marginBottom: 8 }} />
|
||||
<View style={{ height: 12, backgroundColor: colors.surface, borderRadius: 6, width: '50%' }} />
|
||||
</View>
|
||||
);
|
||||
}
|
||||
|
||||
@ -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<TextInput>(null);
|
||||
@ -230,7 +231,7 @@ export function PostCommentsSheet({ postId, visible, onClose }: Props) {
|
||||
<Animated.View
|
||||
style={{
|
||||
flex: 1,
|
||||
backgroundColor: '#ffffff',
|
||||
backgroundColor: colors.bg,
|
||||
borderTopLeftRadius: 24,
|
||||
borderTopRightRadius: 24,
|
||||
overflow: 'hidden',
|
||||
@ -255,7 +256,7 @@ export function PostCommentsSheet({ postId, visible, onClose }: Props) {
|
||||
width: 36,
|
||||
height: 5,
|
||||
borderRadius: 3,
|
||||
backgroundColor: '#d4d4d8',
|
||||
backgroundColor: colors.border,
|
||||
}}
|
||||
/>
|
||||
</View>
|
||||
@ -268,10 +269,10 @@ export function PostCommentsSheet({ postId, visible, onClose }: Props) {
|
||||
paddingTop: 6,
|
||||
paddingBottom: 12,
|
||||
borderBottomWidth: 1,
|
||||
borderBottomColor: '#e5e5e5',
|
||||
borderBottomColor: colors.border,
|
||||
}}
|
||||
>
|
||||
<Text style={{ fontSize: 16, fontFamily: 'Nunito_700Bold', color: '#0a0a0a' }}>
|
||||
<Text style={{ fontSize: 16, fontFamily: 'Nunito_700Bold', color: colors.text }}>
|
||||
{t('community.comments_title')}
|
||||
</Text>
|
||||
</View>
|
||||
@ -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,
|
||||
}}
|
||||
>
|
||||
<Text style={{ fontSize: 12, color: '#737373', fontFamily: 'Nunito_400Regular' }}>
|
||||
<Text style={{ fontSize: 12, color: colors.textMuted, fontFamily: 'Nunito_400Regular' }}>
|
||||
{t('community.reply_to')}{' '}
|
||||
<Text style={{ fontFamily: 'Nunito_700Bold' }}>@{replyTarget.nickname}</Text>
|
||||
</Text>
|
||||
@ -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,
|
||||
}}
|
||||
>
|
||||
<TextInput
|
||||
@ -374,18 +375,18 @@ export function PostCommentsSheet({ postId, visible, onClose }: Props) {
|
||||
value={text}
|
||||
onChangeText={setText}
|
||||
placeholder={t('community.comment_placeholder')}
|
||||
placeholderTextColor="#a3a3a3"
|
||||
placeholderTextColor={colors.textMuted}
|
||||
style={{
|
||||
flex: 1,
|
||||
backgroundColor: '#fafafa',
|
||||
backgroundColor: colors.surface,
|
||||
borderWidth: 1,
|
||||
borderColor: '#e5e5e5',
|
||||
borderColor: colors.border,
|
||||
borderRadius: 999,
|
||||
paddingHorizontal: 16,
|
||||
paddingVertical: 10,
|
||||
fontSize: 14,
|
||||
fontFamily: 'Nunito_400Regular',
|
||||
color: '#0a0a0a',
|
||||
color: colors.text,
|
||||
marginRight: 8,
|
||||
}}
|
||||
returnKeyType="send"
|
||||
@ -430,6 +431,7 @@ type CommentRowProps = {
|
||||
|
||||
function CommentRow({ comment, isReply = false, onReply, onLike }: CommentRowProps) {
|
||||
const { t } = useTranslation();
|
||||
const colors = useColors();
|
||||
const heartScale = useRef(new Animated.Value(1)).current;
|
||||
const handleLikeWithPop = useCallback(() => {
|
||||
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,
|
||||
}}
|
||||
>
|
||||
<Text style={{ fontSize: isReply ? 9 : 11, fontFamily: 'Nunito_700Bold', color: '#737373' }}>
|
||||
<Text style={{ fontSize: isReply ? 9 : 11, fontFamily: 'Nunito_700Bold', color: colors.textMuted }}>
|
||||
{(comment.authorNickname ?? 'AN').slice(0, 2).toUpperCase()}
|
||||
</Text>
|
||||
</View>
|
||||
|
||||
<View style={{ flex: 1, minWidth: 0 }}>
|
||||
<Text style={{ fontSize: 12, fontFamily: 'Nunito_700Bold', color: '#0a0a0a' }}>
|
||||
<Text style={{ fontSize: 12, fontFamily: 'Nunito_700Bold', color: colors.text }}>
|
||||
{comment.authorNickname ?? t('community.anonymous_label')}
|
||||
</Text>
|
||||
<Text
|
||||
style={{
|
||||
fontSize: 14,
|
||||
fontFamily: 'Nunito_400Regular',
|
||||
color: '#404040',
|
||||
color: colors.textMuted,
|
||||
lineHeight: 20,
|
||||
marginTop: 2,
|
||||
}}
|
||||
@ -474,12 +476,12 @@ function CommentRow({ comment, isReply = false, onReply, onLike }: CommentRowPro
|
||||
{comment.content}
|
||||
</Text>
|
||||
<View style={{ flexDirection: 'row', alignItems: 'center', gap: 16, marginTop: 6 }}>
|
||||
<Text style={{ fontSize: 10, color: '#a3a3a3', fontFamily: 'Nunito_400Regular' }}>
|
||||
<Text style={{ fontSize: 10, color: colors.textMuted, fontFamily: 'Nunito_400Regular' }}>
|
||||
{formatRelativeTime(comment.createdAt)}
|
||||
</Text>
|
||||
{!isReply && onReply && (
|
||||
<Pressable onPress={onReply}>
|
||||
<Text style={{ fontSize: 11, color: '#a3a3a3', fontFamily: 'Nunito_600SemiBold' }}>
|
||||
<Text style={{ fontSize: 11, color: colors.textMuted, fontFamily: 'Nunito_600SemiBold' }}>
|
||||
{t('community.reply')}
|
||||
</Text>
|
||||
</Pressable>
|
||||
|
||||
@ -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 (
|
||||
<View className={`items-center bg-white border border-neutral-200 rounded-3xl ${s.padding}`}>
|
||||
<View
|
||||
className={`items-center rounded-3xl ${s.padding}`}
|
||||
style={{ backgroundColor: colors.bg, borderWidth: 1, borderColor: colors.border }}
|
||||
>
|
||||
<View className="flex-row items-center gap-2 mb-1">
|
||||
<Ionicons name="flame" size={s.icon} color={colors.brandOrange} />
|
||||
<Text className={`${s.number} text-neutral-900 tabular-nums`} style={{ fontFamily: 'Nunito_800ExtraBold' }}>{days}</Text>
|
||||
<Text className={`${s.number} tabular-nums`} style={{ fontFamily: 'Nunito_800ExtraBold', color: colors.text }}>{days}</Text>
|
||||
</View>
|
||||
<Text className={`${s.label} text-neutral-500 tracking-wide uppercase`} style={{ fontFamily: 'Nunito_600SemiBold' }}>
|
||||
<Text className={`${s.label} tracking-wide uppercase`} style={{ fontFamily: 'Nunito_600SemiBold', color: colors.textMuted }}>
|
||||
{days === 1 ? t('streak.label_one') : t('streak.label_other')} {t('streak.label_suffix')}
|
||||
</Text>
|
||||
</View>
|
||||
|
||||
@ -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<T> = { value: T; label: string };
|
||||
|
||||
@ -35,6 +35,7 @@ export function WheelPickerModal<T extends string | number>({
|
||||
onSelect,
|
||||
onClose,
|
||||
}: Props<T>) {
|
||||
const colors = useColors();
|
||||
// Tracks the wheel's current selection (separate from confirmed value).
|
||||
// Initialized from `value` prop on each open.
|
||||
const [tempValue, setTempValue] = useState<T | null>(value);
|
||||
@ -72,7 +73,7 @@ export function WheelPickerModal<T extends string | number>({
|
||||
<Pressable onPress={() => {}}>
|
||||
<View
|
||||
style={{
|
||||
backgroundColor: '#ffffff',
|
||||
backgroundColor: colors.bg,
|
||||
borderTopLeftRadius: 18,
|
||||
borderTopRightRadius: 18,
|
||||
paddingBottom: 24,
|
||||
@ -87,7 +88,7 @@ export function WheelPickerModal<T extends string | number>({
|
||||
paddingHorizontal: 16,
|
||||
paddingVertical: 12,
|
||||
borderBottomWidth: 1,
|
||||
borderBottomColor: '#e5e5e5',
|
||||
borderBottomColor: colors.border,
|
||||
}}
|
||||
>
|
||||
<Pressable onPress={onClose} hitSlop={10}>
|
||||
|
||||
@ -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 */}
|
||||
<View style={{ alignItems: 'center', paddingTop: 8, paddingBottom: 4 }}>
|
||||
<View style={{ width: 36, height: 4, borderRadius: 2, backgroundColor: '#d4d4d4' }} />
|
||||
<View style={{ width: 36, height: 4, borderRadius: 2, backgroundColor: colors.border }} />
|
||||
</View>
|
||||
|
||||
{/* Header */}
|
||||
@ -147,15 +149,15 @@ export function AddDomainSheet({ visible, tier, onClose, onAdd }: Props) {
|
||||
paddingTop: 6,
|
||||
paddingBottom: 12,
|
||||
borderBottomWidth: 1,
|
||||
borderBottomColor: '#f0f0f0',
|
||||
borderBottomColor: colors.border,
|
||||
}}
|
||||
>
|
||||
<Pressable onPress={close} hitSlop={10}>
|
||||
<Text style={{ fontSize: 16, fontFamily: 'Nunito_400Regular', color: '#525252' }}>
|
||||
<Text style={{ fontSize: 16, fontFamily: 'Nunito_400Regular', color: colors.textMuted }}>
|
||||
{t('common.cancel')}
|
||||
</Text>
|
||||
</Pressable>
|
||||
<Text style={{ fontSize: 16, fontFamily: 'Nunito_700Bold', color: '#0a0a0a' }}>
|
||||
<Text style={{ fontSize: 16, fontFamily: 'Nunito_700Bold', color: colors.text }}>
|
||||
{t('blocker.add_sheet_title')}
|
||||
</Text>
|
||||
<View style={{ width: 60 }} />
|
||||
@ -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,
|
||||
}}
|
||||
>
|
||||
|
||||
@ -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() {
|
||||
|
||||
@ -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}
|
||||
>
|
||||
<View style={{ flex: 1, backgroundColor: '#fff' }}>
|
||||
<View style={{ flex: 1, backgroundColor: colors.bg }}>
|
||||
{/* Header */}
|
||||
<View
|
||||
style={{
|
||||
@ -85,29 +87,29 @@ export function DeactivationExplainerSheet({
|
||||
paddingTop: 14,
|
||||
paddingBottom: 12,
|
||||
borderBottomWidth: 1,
|
||||
borderBottomColor: '#f0f0f0',
|
||||
borderBottomColor: colors.border,
|
||||
}}
|
||||
>
|
||||
<Pressable onPress={onClose} hitSlop={10}>
|
||||
<Text style={{ fontSize: 16, fontFamily: 'Nunito_400Regular', color: '#525252' }}>
|
||||
<Text style={{ fontSize: 16, fontFamily: 'Nunito_400Regular', color: colors.textMuted }}>
|
||||
{t('common.back')}
|
||||
</Text>
|
||||
</Pressable>
|
||||
<Text style={{ fontSize: 16, fontFamily: 'Nunito_700Bold', color: '#0a0a0a' }}>
|
||||
<Text style={{ fontSize: 16, fontFamily: 'Nunito_700Bold', color: colors.text }}>
|
||||
{t('blocker.deactivation_heading')}
|
||||
</Text>
|
||||
<View style={{ width: 50 }} />
|
||||
</View>
|
||||
|
||||
<ScrollView contentContainerStyle={{ padding: 20, gap: 18 }}>
|
||||
<Text style={{ fontSize: 22, fontFamily: 'Nunito_800ExtraBold', color: '#0a0a0a' }}>
|
||||
<Text style={{ fontSize: 22, fontFamily: 'Nunito_800ExtraBold', color: colors.text }}>
|
||||
{t('blocker.deactivation_title')}
|
||||
</Text>
|
||||
<Text
|
||||
style={{
|
||||
fontSize: 15,
|
||||
fontFamily: 'Nunito_400Regular',
|
||||
color: '#404040',
|
||||
color: colors.textMuted,
|
||||
lineHeight: 22,
|
||||
}}
|
||||
>
|
||||
@ -195,6 +197,7 @@ function BulletRow({
|
||||
title: string;
|
||||
text: string;
|
||||
}) {
|
||||
const colors = useColors();
|
||||
return (
|
||||
<View style={{ flexDirection: 'row', gap: 12 }}>
|
||||
<View
|
||||
@ -202,22 +205,22 @@ function BulletRow({
|
||||
width: 36,
|
||||
height: 36,
|
||||
borderRadius: 10,
|
||||
backgroundColor: '#f5f5f5',
|
||||
backgroundColor: colors.surfaceElevated,
|
||||
alignItems: 'center',
|
||||
justifyContent: 'center',
|
||||
}}
|
||||
>
|
||||
<Ionicons name={icon} size={18} color="#525252" />
|
||||
<Ionicons name={icon} size={18} color={colors.textMuted} />
|
||||
</View>
|
||||
<View style={{ flex: 1 }}>
|
||||
<Text style={{ fontSize: 14, fontFamily: 'Nunito_700Bold', color: '#0a0a0a' }}>
|
||||
<Text style={{ fontSize: 14, fontFamily: 'Nunito_700Bold', color: colors.text }}>
|
||||
{title}
|
||||
</Text>
|
||||
<Text
|
||||
style={{
|
||||
fontSize: 12,
|
||||
fontFamily: 'Nunito_400Regular',
|
||||
color: '#525252',
|
||||
color: colors.textMuted,
|
||||
marginTop: 2,
|
||||
lineHeight: 17,
|
||||
}}
|
||||
|
||||
@ -11,6 +11,7 @@ import { useTranslation } from 'react-i18next';
|
||||
import { SuccessAlert } from '../SuccessAlert';
|
||||
import { ConfirmAlert } from '../ConfirmAlert';
|
||||
import type { CustomDomain, Tier } from '../../hooks/useCustomDomains';
|
||||
import { useColors } from '../../lib/theme';
|
||||
|
||||
// ─── Helpers ─────────────────────────────────────────────────────────────
|
||||
|
||||
@ -65,6 +66,7 @@ const STATUS_PRIORITY: Record<string, number> = {
|
||||
|
||||
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
|
||||
<View style={{ gap: 12 }}>
|
||||
{/* Header: Section-Title + Slot-Counter + Add-Button (inline, neben SlotPill) */}
|
||||
<View style={{ flexDirection: 'row', alignItems: 'center', justifyContent: 'space-between' }}>
|
||||
<Text style={{ fontSize: 14, fontFamily: 'Nunito_700Bold', color: '#0a0a0a' }}>
|
||||
<Text style={{ fontSize: 14, fontFamily: 'Nunito_700Bold', color: colors.text }}>
|
||||
{t('blocker.domain_section_title')}
|
||||
</Text>
|
||||
<View style={{ flexDirection: 'row', alignItems: 'center', gap: 8 }}>
|
||||
@ -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 (
|
||||
<View style={{ height: 4, borderRadius: 2, backgroundColor: '#f0f0f0', overflow: 'hidden' }}>
|
||||
<View style={{ height: 4, borderRadius: 2, backgroundColor: colors.surfaceElevated, overflow: 'hidden' }}>
|
||||
<View
|
||||
style={{
|
||||
height: '100%',
|
||||
@ -174,16 +176,16 @@ export function DomainGrid({ domains, tier, onAdd, onSubmit, onUpgradePro }: Pro
|
||||
borderRadius: 14,
|
||||
borderWidth: 1,
|
||||
borderStyle: 'dashed',
|
||||
borderColor: '#d4d4d4',
|
||||
borderColor: colors.border,
|
||||
alignItems: 'center',
|
||||
}}
|
||||
>
|
||||
<Ionicons name="globe-outline" size={28} color="#a3a3a3" />
|
||||
<Ionicons name="globe-outline" size={28} color={colors.textMuted} />
|
||||
<Text
|
||||
style={{
|
||||
fontSize: 13,
|
||||
fontFamily: 'Nunito_400Regular',
|
||||
color: '#737373',
|
||||
color: colors.textMuted,
|
||||
marginTop: 8,
|
||||
textAlign: 'center',
|
||||
}}
|
||||
@ -205,8 +207,9 @@ export function DomainGrid({ domains, tier, onAdd, onSubmit, onUpgradePro }: Pro
|
||||
// ─── SlotPill ─────────────────────────────────────────────────────────────
|
||||
|
||||
function SlotPill({ tier }: { tier: Tier }) {
|
||||
const bg = tier.atLimit ? '#fee2e2' : '#f5f5f5';
|
||||
const fg = tier.atLimit ? '#dc2626' : '#525252';
|
||||
const colors = useColors();
|
||||
const bg = tier.atLimit ? '#fee2e2' : colors.surfaceElevated;
|
||||
const fg = tier.atLimit ? '#dc2626' : colors.textMuted;
|
||||
return (
|
||||
<View
|
||||
style={{
|
||||
@ -258,6 +261,7 @@ function DomainTile({
|
||||
onSubmit?: (id: string) => 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 (
|
||||
<View
|
||||
style={{
|
||||
backgroundColor: '#fff',
|
||||
backgroundColor: colors.bg,
|
||||
borderWidth: 1,
|
||||
borderColor: '#e5e5e5',
|
||||
borderColor: colors.border,
|
||||
borderRadius: 14,
|
||||
padding: 8,
|
||||
// KEIN aspectRatio:1 mehr — der hat den Button auf 0 Höhe gepresst.
|
||||
@ -417,7 +421,7 @@ function DomainTile({
|
||||
style={{
|
||||
fontSize: 10,
|
||||
fontFamily: 'Nunito_600SemiBold',
|
||||
color: '#0a0a0a',
|
||||
color: colors.text,
|
||||
textAlign: 'center',
|
||||
width: '100%',
|
||||
}}
|
||||
|
||||
@ -2,7 +2,7 @@ import { View, Text, Switch, Pressable, ActivityIndicator } from 'react-native';
|
||||
import { Ionicons } from '@expo/vector-icons';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
import type { ProtectionState } from '../../lib/protection';
|
||||
import { colors } from '../../lib/theme';
|
||||
import { useColors } from '../../lib/theme';
|
||||
|
||||
type Props = {
|
||||
state: ProtectionState;
|
||||
@ -15,6 +15,7 @@ type Props = {
|
||||
|
||||
export function ProtectionCard({ state, loading, onActivate, onPressSettings }: Props) {
|
||||
const { t } = useTranslation();
|
||||
const colors = useColors();
|
||||
const isActive = state.phase === 'active' || state.phase === 'cooldownActive';
|
||||
const isCooldown = state.phase === 'cooldownActive';
|
||||
|
||||
@ -30,8 +31,8 @@ export function ProtectionCard({ state, loading, onActivate, onPressSettings }:
|
||||
return t('blocker.protection_subtitle_pro');
|
||||
})();
|
||||
|
||||
const cardBg = isCooldown ? '#fef3c7' : isActive ? '#dcfce7' : '#ffffff';
|
||||
const cardBorder = isCooldown ? '#fcd34d' : isActive ? '#86efac' : '#e5e5e5';
|
||||
const cardBg = isCooldown ? '#fef3c7' : isActive ? '#dcfce7' : colors.bg;
|
||||
const cardBorder = isCooldown ? '#fcd34d' : isActive ? '#86efac' : colors.border;
|
||||
const iconBg = isCooldown ? '#fde68a' : isActive ? '#bbf7d0' : '#f5f5f5';
|
||||
const iconColor = isCooldown ? '#d97706' : isActive ? '#16a34a' : '#a3a3a3';
|
||||
|
||||
@ -67,7 +68,7 @@ export function ProtectionCard({ state, loading, onActivate, onPressSettings }:
|
||||
style={{
|
||||
fontSize: 15,
|
||||
fontFamily: 'Nunito_700Bold',
|
||||
color: '#0a0a0a',
|
||||
color: colors.text,
|
||||
}}
|
||||
>
|
||||
{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,
|
||||
}}>
|
||||
<Ionicons name="settings-outline" size={18} color="#525252" />
|
||||
<Ionicons name="settings-outline" size={18} color={colors.textMuted} />
|
||||
</View>
|
||||
</Pressable>
|
||||
) : (
|
||||
@ -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 (
|
||||
<View style={{ flex: 1, alignItems: 'center' }}>
|
||||
<Text style={{ fontSize: 16, fontFamily: 'Nunito_800ExtraBold', color: valueColor }}>
|
||||
<Text style={{ fontSize: 16, fontFamily: 'Nunito_800ExtraBold', color: valueColor ?? colors.text }}>
|
||||
{value}
|
||||
</Text>
|
||||
<Text style={{ fontSize: 11, fontFamily: 'Nunito_400Regular', color: '#737373' }}>
|
||||
<Text style={{ fontSize: 11, fontFamily: 'Nunito_400Regular', color: colors.textMuted }}>
|
||||
{label}
|
||||
</Text>
|
||||
</View>
|
||||
|
||||
@ -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({
|
||||
<Animated.View
|
||||
style={{
|
||||
flex: 1,
|
||||
backgroundColor: '#fff',
|
||||
backgroundColor: colors.bg,
|
||||
borderTopLeftRadius: 24,
|
||||
borderTopRightRadius: 24,
|
||||
overflow: 'hidden',
|
||||
@ -178,7 +180,7 @@ export function ProtectionDetailsSheet({
|
||||
{...panResponder.panHandlers}
|
||||
style={{ alignItems: 'center', paddingTop: 8, paddingBottom: 6 }}
|
||||
>
|
||||
<View style={{ width: 36, height: 5, borderRadius: 3, backgroundColor: '#d4d4d8' }} />
|
||||
<View style={{ width: 36, height: 5, borderRadius: 3, backgroundColor: colors.border }} />
|
||||
</View>
|
||||
|
||||
{/* Header */}
|
||||
@ -192,15 +194,15 @@ export function ProtectionDetailsSheet({
|
||||
paddingTop: 4,
|
||||
paddingBottom: 12,
|
||||
borderBottomWidth: 1,
|
||||
borderBottomColor: '#f0f0f0',
|
||||
borderBottomColor: colors.border,
|
||||
}}
|
||||
>
|
||||
<View style={{ width: 50 }} />
|
||||
<Text style={{ fontSize: 16, fontFamily: 'Nunito_700Bold', color: '#0a0a0a' }}>
|
||||
<Text style={{ fontSize: 16, fontFamily: 'Nunito_700Bold', color: colors.text }}>
|
||||
{t('blocker.details_title')}
|
||||
</Text>
|
||||
<Pressable onPress={handleClose} hitSlop={10} style={{ width: 50, alignItems: 'flex-end' }}>
|
||||
<Text style={{ fontSize: 16, fontFamily: 'Nunito_400Regular', color: '#525252' }}>
|
||||
<Text style={{ fontSize: 16, fontFamily: 'Nunito_400Regular', color: colors.textMuted }}>
|
||||
{t('blocker.details_done')}
|
||||
</Text>
|
||||
</Pressable>
|
||||
@ -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,
|
||||
}}
|
||||
>
|
||||
<View>
|
||||
<Text style={{ fontSize: 13, color: '#0a0a0a', fontFamily: 'Nunito_700Bold' }}>
|
||||
<Text style={{ fontSize: 13, color: colors.text, fontFamily: 'Nunito_700Bold' }}>
|
||||
{t('blocker.kpi_submissions_title')}
|
||||
</Text>
|
||||
<Text style={{ fontSize: 11, color: '#737373', fontFamily: 'Nunito_400Regular', marginTop: 2 }}>
|
||||
<Text style={{ fontSize: 11, color: colors.textMuted, fontFamily: 'Nunito_400Regular', marginTop: 2 }}>
|
||||
{t('blocker.kpi_submissions_subtitle')}
|
||||
</Text>
|
||||
</View>
|
||||
@ -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')}
|
||||
</Text>
|
||||
<Ionicons name="help-circle-outline" size={18} color="#737373" />
|
||||
<Ionicons name="help-circle-outline" size={18} color={colors.textMuted} />
|
||||
</View>
|
||||
{[1, 2, 3, 4].map((n) => (
|
||||
<FaqItem
|
||||
@ -355,7 +357,7 @@ export function ProtectionDetailsSheet({
|
||||
borderRadius: 12,
|
||||
borderWidth: 1.5,
|
||||
borderColor: HERO_COLOR,
|
||||
backgroundColor: '#fff7ed',
|
||||
backgroundColor: colors.surface,
|
||||
flexDirection: 'row',
|
||||
alignItems: 'center',
|
||||
justifyContent: 'center',
|
||||
@ -479,6 +481,7 @@ function KpiCard({
|
||||
decimals?: number;
|
||||
suffix?: string;
|
||||
}) {
|
||||
const colors = useColors();
|
||||
return (
|
||||
<View
|
||||
style={{
|
||||
@ -486,14 +489,14 @@ function KpiCard({
|
||||
padding: 12,
|
||||
borderRadius: 12,
|
||||
borderWidth: 1,
|
||||
borderColor: '#e5e5e5',
|
||||
backgroundColor: '#fafafa',
|
||||
borderColor: colors.border,
|
||||
backgroundColor: colors.surface,
|
||||
gap: 6,
|
||||
}}
|
||||
>
|
||||
<View style={{ flexDirection: 'row', alignItems: 'center', gap: 6 }}>
|
||||
<Ionicons name={icon} size={14} color="#737373" />
|
||||
<Text style={{ flex: 1, fontSize: 10, color: '#737373', fontFamily: 'Nunito_400Regular', lineHeight: 13 }}>
|
||||
<Ionicons name={icon} size={14} color={colors.textMuted} />
|
||||
<Text style={{ flex: 1, fontSize: 10, color: colors.textMuted, fontFamily: 'Nunito_400Regular', lineHeight: 13 }}>
|
||||
{label}
|
||||
</Text>
|
||||
</View>
|
||||
@ -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 ? (
|
||||
<Text style={{ fontSize: 10, color: '#737373', fontFamily: 'Nunito_700Bold' }}>{suffix}</Text>
|
||||
<Text style={{ fontSize: 10, color: colors.textMuted, fontFamily: 'Nunito_700Bold' }}>{suffix}</Text>
|
||||
) : null}
|
||||
</View>
|
||||
</View>
|
||||
@ -522,13 +525,14 @@ function LegendItem({
|
||||
label: string;
|
||||
value: number;
|
||||
}) {
|
||||
const colors = useColors();
|
||||
return (
|
||||
<View style={{ alignItems: 'center', gap: 4 }}>
|
||||
<View style={{ flexDirection: 'row', alignItems: 'center', gap: 5 }}>
|
||||
<View style={{ width: 8, height: 8, borderRadius: 4, backgroundColor: color }} />
|
||||
<Text style={{ fontSize: 11, color: '#525252', fontFamily: 'Nunito_700Bold' }}>{value}</Text>
|
||||
<Text style={{ fontSize: 11, color: colors.textMuted, fontFamily: 'Nunito_700Bold' }}>{value}</Text>
|
||||
</View>
|
||||
<Text style={{ fontSize: 10, color: '#737373', fontFamily: 'Nunito_400Regular' }}>{label}</Text>
|
||||
<Text style={{ fontSize: 10, color: colors.textMuted, fontFamily: 'Nunito_400Regular' }}>{label}</Text>
|
||||
</View>
|
||||
);
|
||||
}
|
||||
@ -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 */}
|
||||
<Path
|
||||
d={arcPath(cx, cy, r, 180, 360)}
|
||||
stroke="#f0f0f0"
|
||||
stroke={colors.surfaceElevated}
|
||||
strokeWidth={stroke}
|
||||
fill="none"
|
||||
strokeLinecap="round"
|
||||
@ -618,10 +623,10 @@ function HalfDonut({
|
||||
alignItems: 'center',
|
||||
}}
|
||||
>
|
||||
<Text style={{ fontSize: 30, fontFamily: 'Nunito_900Black', color: '#0a0a0a', letterSpacing: -0.5 }}>
|
||||
<Text style={{ fontSize: 30, fontFamily: 'Nunito_900Black', color: colors.text, letterSpacing: -0.5 }}>
|
||||
{centerValue}
|
||||
</Text>
|
||||
<Text style={{ fontSize: 10, color: '#737373', fontFamily: 'Nunito_400Regular', marginTop: -2 }}>
|
||||
<Text style={{ fontSize: 10, color: colors.textMuted, fontFamily: 'Nunito_400Regular', marginTop: -2 }}>
|
||||
{centerLabel}
|
||||
</Text>
|
||||
</View>
|
||||
@ -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,
|
||||
}}
|
||||
>
|
||||
<Pressable
|
||||
@ -678,7 +684,7 @@ function FaqItem({ question, answer }: { question: string; answer: string }) {
|
||||
>
|
||||
<View style={{ flexDirection: 'row', alignItems: 'center', paddingHorizontal: 14, paddingVertical: 14 }}>
|
||||
<View style={{ flex: 1, paddingRight: 12 }}>
|
||||
<Text style={{ fontSize: 13, fontFamily: 'Nunito_700Bold', color: '#0a0a0a', lineHeight: 18 }}>
|
||||
<Text style={{ fontSize: 13, fontFamily: 'Nunito_700Bold', color: colors.text, lineHeight: 18 }}>
|
||||
{question}
|
||||
</Text>
|
||||
</View>
|
||||
@ -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 }],
|
||||
}}
|
||||
>
|
||||
<Ionicons name="chevron-down" size={16} color="#525252" />
|
||||
<Ionicons name="chevron-down" size={16} color={colors.textMuted} />
|
||||
</Animated.View>
|
||||
</View>
|
||||
</Pressable>
|
||||
{open && (
|
||||
<View style={{ paddingHorizontal: 14, paddingBottom: 14, paddingTop: 0 }}>
|
||||
<Text style={{ fontSize: 13, fontFamily: 'Nunito_400Regular', color: '#525252', lineHeight: 19 }}>
|
||||
<Text style={{ fontSize: 13, fontFamily: 'Nunito_400Regular', color: colors.textMuted, lineHeight: 19 }}>
|
||||
{answer}
|
||||
</Text>
|
||||
</View>
|
||||
|
||||
@ -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) {
|
||||
<Ionicons name="shield-checkmark" size={22} color={iconColor} />
|
||||
</View>
|
||||
<View style={{ flex: 1 }}>
|
||||
<Text style={{ fontSize: 15, fontFamily: 'Nunito_700Bold', color: '#0a0a0a' }}>
|
||||
<Text style={{ fontSize: 15, fontFamily: 'Nunito_700Bold', color: colors.text }}>
|
||||
{t('blocker.protection_card_locked_title')}
|
||||
</Text>
|
||||
<Text
|
||||
style={{
|
||||
fontSize: 12,
|
||||
fontFamily: 'Nunito_400Regular',
|
||||
color: '#525252',
|
||||
color: colors.textMuted,
|
||||
marginTop: 2,
|
||||
}}
|
||||
>
|
||||
@ -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,
|
||||
}}>
|
||||
<Ionicons name="settings-outline" size={18} color="#525252" />
|
||||
<Ionicons name="settings-outline" size={18} color={colors.textMuted} />
|
||||
</View>
|
||||
</Pressable>
|
||||
</View>
|
||||
@ -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 (
|
||||
<View style={{ flex: 1, alignItems: 'center' }}>
|
||||
<Text style={{ fontSize: 16, fontFamily: 'Nunito_800ExtraBold', color: valueColor }}>{value}</Text>
|
||||
<Text style={{ fontSize: 11, fontFamily: 'Nunito_400Regular', color: '#737373' }}>{label}</Text>
|
||||
<Text style={{ fontSize: 16, fontFamily: 'Nunito_800ExtraBold', color: valueColor ?? colors.text }}>{value}</Text>
|
||||
<Text style={{ fontSize: 11, fontFamily: 'Nunito_400Regular', color: colors.textMuted }}>{label}</Text>
|
||||
</View>
|
||||
);
|
||||
}
|
||||
|
||||
@ -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<ReturnType<typeof setTimeout> | null>(null);
|
||||
|
||||
@ -323,7 +326,8 @@ export function ChatBubble({
|
||||
);
|
||||
}
|
||||
|
||||
const styles = StyleSheet.create({
|
||||
function makeStyles(colors: ReturnType<typeof useColors>) {
|
||||
return StyleSheet.create({
|
||||
row: {
|
||||
flexDirection: 'row',
|
||||
paddingHorizontal: 8,
|
||||
@ -337,7 +341,7 @@ const styles = StyleSheet.create({
|
||||
width: 26,
|
||||
height: 26,
|
||||
borderRadius: 13,
|
||||
backgroundColor: '#e5e5e5',
|
||||
backgroundColor: colors.surfaceElevated,
|
||||
},
|
||||
bubbleCol: {
|
||||
maxWidth: '78%',
|
||||
@ -362,9 +366,9 @@ const styles = StyleSheet.create({
|
||||
backgroundColor: '#007AFF',
|
||||
},
|
||||
bubbleOther: {
|
||||
backgroundColor: '#ffffff',
|
||||
backgroundColor: colors.surface,
|
||||
borderWidth: StyleSheet.hairlineWidth,
|
||||
borderColor: '#e5e5e5',
|
||||
borderColor: colors.border,
|
||||
},
|
||||
replyPreview: {
|
||||
borderLeftWidth: 3,
|
||||
@ -381,7 +385,7 @@ const styles = StyleSheet.create({
|
||||
image: {
|
||||
width: 220,
|
||||
height: 220,
|
||||
backgroundColor: '#f5f5f5',
|
||||
backgroundColor: colors.surfaceElevated,
|
||||
},
|
||||
imageTimeOverlay: {
|
||||
position: 'absolute',
|
||||
@ -412,7 +416,7 @@ const styles = StyleSheet.create({
|
||||
justifyContent: 'flex-end',
|
||||
},
|
||||
sheet: {
|
||||
backgroundColor: '#fff',
|
||||
backgroundColor: colors.bg,
|
||||
borderTopLeftRadius: 20,
|
||||
borderTopRightRadius: 20,
|
||||
padding: 8,
|
||||
@ -422,7 +426,7 @@ const styles = StyleSheet.create({
|
||||
width: 36,
|
||||
height: 4,
|
||||
borderRadius: 2,
|
||||
backgroundColor: '#d4d4d4',
|
||||
backgroundColor: colors.border,
|
||||
alignSelf: 'center',
|
||||
marginBottom: 10,
|
||||
},
|
||||
@ -436,7 +440,8 @@ const styles = StyleSheet.create({
|
||||
sheetText: {
|
||||
fontSize: 15,
|
||||
fontFamily: 'Nunito_600SemiBold',
|
||||
color: '#171717',
|
||||
color: colors.text,
|
||||
marginLeft: 12,
|
||||
},
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
@ -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 (
|
||||
<View style={styles.container}>
|
||||
{/* Reply preview */}
|
||||
@ -231,18 +235,19 @@ function decodeBase64(base64: string): Uint8Array {
|
||||
return bytes;
|
||||
}
|
||||
|
||||
const styles = StyleSheet.create({
|
||||
function makeStyles(colors: ReturnType<typeof useColors>) {
|
||||
return StyleSheet.create({
|
||||
container: {
|
||||
backgroundColor: '#ffffff',
|
||||
backgroundColor: colors.bg,
|
||||
borderTopWidth: StyleSheet.hairlineWidth,
|
||||
borderTopColor: '#e5e5e5',
|
||||
borderTopColor: colors.border,
|
||||
},
|
||||
replyBar: {
|
||||
flexDirection: 'row',
|
||||
alignItems: 'center',
|
||||
paddingHorizontal: 12,
|
||||
paddingVertical: 8,
|
||||
backgroundColor: '#eff6ff',
|
||||
backgroundColor: colors.surface,
|
||||
borderLeftWidth: 3,
|
||||
borderLeftColor: '#007AFF',
|
||||
marginHorizontal: 8,
|
||||
@ -257,7 +262,7 @@ const styles = StyleSheet.create({
|
||||
replyContent: {
|
||||
fontSize: 11,
|
||||
fontFamily: 'Nunito_400Regular',
|
||||
color: '#525252',
|
||||
color: colors.textMuted,
|
||||
marginTop: 1,
|
||||
},
|
||||
attachBar: {
|
||||
@ -265,7 +270,7 @@ const styles = StyleSheet.create({
|
||||
alignItems: 'center',
|
||||
paddingHorizontal: 12,
|
||||
paddingVertical: 6,
|
||||
backgroundColor: '#fafafa',
|
||||
backgroundColor: colors.surface,
|
||||
marginHorizontal: 8,
|
||||
marginTop: 6,
|
||||
borderRadius: 8,
|
||||
@ -280,7 +285,7 @@ const styles = StyleSheet.create({
|
||||
width: 36,
|
||||
height: 36,
|
||||
borderRadius: 6,
|
||||
backgroundColor: '#e5e5e5',
|
||||
backgroundColor: colors.surfaceElevated,
|
||||
alignItems: 'center',
|
||||
justifyContent: 'center',
|
||||
marginRight: 8,
|
||||
@ -289,7 +294,7 @@ const styles = StyleSheet.create({
|
||||
flex: 1,
|
||||
fontSize: 12,
|
||||
fontFamily: 'Nunito_600SemiBold',
|
||||
color: '#171717',
|
||||
color: colors.text,
|
||||
},
|
||||
row: {
|
||||
flexDirection: 'row',
|
||||
@ -308,7 +313,7 @@ const styles = StyleSheet.create({
|
||||
},
|
||||
inputWrap: {
|
||||
flex: 1,
|
||||
backgroundColor: '#f5f5f5',
|
||||
backgroundColor: colors.surfaceElevated,
|
||||
borderRadius: 22,
|
||||
paddingHorizontal: 14,
|
||||
minHeight: 36,
|
||||
@ -319,7 +324,7 @@ const styles = StyleSheet.create({
|
||||
fontSize: 14,
|
||||
lineHeight: 19,
|
||||
fontFamily: 'Nunito_400Regular',
|
||||
color: '#171717',
|
||||
color: colors.text,
|
||||
paddingVertical: Platform.OS === 'ios' ? 8 : 4,
|
||||
},
|
||||
sendBtn: {
|
||||
@ -330,4 +335,5 @@ const styles = StyleSheet.create({
|
||||
justifyContent: 'center',
|
||||
marginLeft: 6,
|
||||
},
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
@ -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,14 +148,15 @@ export function CreateRoomSheet({ visible, onClose, onCreated }: Props) {
|
||||
);
|
||||
}
|
||||
|
||||
const styles = StyleSheet.create({
|
||||
function makeStyles(colors: ReturnType<typeof useColors>) {
|
||||
return StyleSheet.create({
|
||||
backdrop: {
|
||||
flex: 1,
|
||||
backgroundColor: 'rgba(0,0,0,0.5)',
|
||||
justifyContent: 'flex-end',
|
||||
},
|
||||
sheet: {
|
||||
backgroundColor: '#fff',
|
||||
backgroundColor: colors.bg,
|
||||
borderTopLeftRadius: 22,
|
||||
borderTopRightRadius: 22,
|
||||
padding: 18,
|
||||
@ -162,24 +166,24 @@ const styles = StyleSheet.create({
|
||||
width: 36,
|
||||
height: 4,
|
||||
borderRadius: 2,
|
||||
backgroundColor: '#d4d4d4',
|
||||
backgroundColor: colors.border,
|
||||
alignSelf: 'center',
|
||||
marginBottom: 12,
|
||||
},
|
||||
title: {
|
||||
fontSize: 17,
|
||||
fontFamily: 'Nunito_700Bold',
|
||||
color: '#171717',
|
||||
color: colors.text,
|
||||
marginBottom: 14,
|
||||
},
|
||||
input: {
|
||||
backgroundColor: '#f5f5f5',
|
||||
backgroundColor: colors.surfaceElevated,
|
||||
borderRadius: 12,
|
||||
paddingHorizontal: 14,
|
||||
paddingVertical: 12,
|
||||
fontSize: 14,
|
||||
fontFamily: 'Nunito_400Regular',
|
||||
color: '#171717',
|
||||
color: colors.text,
|
||||
marginBottom: 10,
|
||||
},
|
||||
toggleRow: {
|
||||
@ -192,13 +196,13 @@ const styles = StyleSheet.create({
|
||||
toggleLabel: {
|
||||
fontSize: 14,
|
||||
fontFamily: 'Nunito_600SemiBold',
|
||||
color: '#171717',
|
||||
color: colors.text,
|
||||
},
|
||||
toggle: {
|
||||
width: 46,
|
||||
height: 28,
|
||||
borderRadius: 14,
|
||||
backgroundColor: '#e5e5e5',
|
||||
backgroundColor: colors.surfaceElevated,
|
||||
padding: 2,
|
||||
justifyContent: 'center',
|
||||
},
|
||||
@ -209,7 +213,7 @@ const styles = StyleSheet.create({
|
||||
width: 24,
|
||||
height: 24,
|
||||
borderRadius: 12,
|
||||
backgroundColor: '#fff',
|
||||
backgroundColor: colors.bg,
|
||||
shadowColor: '#000',
|
||||
shadowOpacity: 0.15,
|
||||
shadowRadius: 2,
|
||||
@ -222,7 +226,7 @@ const styles = StyleSheet.create({
|
||||
subLabel: {
|
||||
fontSize: 12,
|
||||
fontFamily: 'Nunito_600SemiBold',
|
||||
color: '#737373',
|
||||
color: colors.textMuted,
|
||||
marginBottom: 6,
|
||||
},
|
||||
modeRow: {
|
||||
@ -233,18 +237,18 @@ const styles = StyleSheet.create({
|
||||
paddingVertical: 8,
|
||||
borderRadius: 10,
|
||||
borderWidth: 1,
|
||||
borderColor: '#e5e5e5',
|
||||
borderColor: colors.border,
|
||||
alignItems: 'center',
|
||||
marginRight: 6,
|
||||
},
|
||||
modeBtnActive: {
|
||||
backgroundColor: '#eff6ff',
|
||||
backgroundColor: colors.surface,
|
||||
borderColor: '#007AFF',
|
||||
},
|
||||
modeBtnText: {
|
||||
fontSize: 12,
|
||||
fontFamily: 'Nunito_600SemiBold',
|
||||
color: '#737373',
|
||||
color: colors.textMuted,
|
||||
},
|
||||
modeBtnTextActive: {
|
||||
color: '#007AFF',
|
||||
@ -255,7 +259,7 @@ const styles = StyleSheet.create({
|
||||
},
|
||||
cancelBtn: {
|
||||
flex: 1,
|
||||
backgroundColor: '#f5f5f5',
|
||||
backgroundColor: colors.surfaceElevated,
|
||||
paddingVertical: 12,
|
||||
borderRadius: 12,
|
||||
alignItems: 'center',
|
||||
@ -264,7 +268,7 @@ const styles = StyleSheet.create({
|
||||
cancelText: {
|
||||
fontSize: 14,
|
||||
fontFamily: 'Nunito_600SemiBold',
|
||||
color: '#171717',
|
||||
color: colors.text,
|
||||
},
|
||||
createBtn: {
|
||||
flex: 1,
|
||||
@ -279,4 +283,5 @@ const styles = StyleSheet.create({
|
||||
fontFamily: 'Nunito_700Bold',
|
||||
color: '#fff',
|
||||
},
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
@ -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) {
|
||||
<View
|
||||
style={[
|
||||
styles.avatar,
|
||||
{ backgroundColor: room.isPublic ? '#eff6ff' : '#e5e5e5' },
|
||||
{ backgroundColor: room.isPublic ? colors.surface : colors.surfaceElevated },
|
||||
]}
|
||||
>
|
||||
{room.avatarUrl ? (
|
||||
@ -102,16 +105,17 @@ export function RoomCard({ room, onPress }: Props) {
|
||||
);
|
||||
}
|
||||
|
||||
const styles = StyleSheet.create({
|
||||
function makeStyles(colors: ReturnType<typeof useColors>) {
|
||||
return StyleSheet.create({
|
||||
row: {
|
||||
width: '100%',
|
||||
flexDirection: 'row',
|
||||
alignItems: 'center',
|
||||
paddingHorizontal: 14,
|
||||
paddingVertical: 11,
|
||||
backgroundColor: '#fff',
|
||||
backgroundColor: colors.bg,
|
||||
borderBottomWidth: StyleSheet.hairlineWidth,
|
||||
borderBottomColor: '#f5f5f5',
|
||||
borderBottomColor: colors.border,
|
||||
},
|
||||
avatar: {
|
||||
width: 42,
|
||||
@ -129,7 +133,7 @@ const styles = StyleSheet.create({
|
||||
avatarInitials: {
|
||||
fontSize: 13,
|
||||
fontFamily: 'Nunito_700Bold',
|
||||
color: '#525252',
|
||||
color: colors.textMuted,
|
||||
},
|
||||
info: {
|
||||
flex: 1,
|
||||
@ -155,19 +159,19 @@ const styles = StyleSheet.create({
|
||||
paddingHorizontal: 6,
|
||||
paddingVertical: 2,
|
||||
borderRadius: 8,
|
||||
backgroundColor: '#f5f5f5',
|
||||
backgroundColor: colors.surfaceElevated,
|
||||
},
|
||||
name: {
|
||||
fontSize: 14,
|
||||
fontFamily: 'Nunito_700Bold',
|
||||
color: '#171717',
|
||||
color: colors.text,
|
||||
flexShrink: 1,
|
||||
},
|
||||
defaultBadge: {
|
||||
marginLeft: 6,
|
||||
paddingHorizontal: 6,
|
||||
paddingVertical: 1,
|
||||
backgroundColor: '#eff6ff',
|
||||
backgroundColor: colors.surface,
|
||||
borderRadius: 8,
|
||||
},
|
||||
defaultBadgeText: {
|
||||
@ -178,12 +182,12 @@ const styles = StyleSheet.create({
|
||||
lastMessage: {
|
||||
fontSize: 12,
|
||||
fontFamily: 'Nunito_400Regular',
|
||||
color: '#737373',
|
||||
color: colors.textMuted,
|
||||
},
|
||||
description: {
|
||||
fontSize: 12,
|
||||
fontFamily: 'Nunito_400Regular',
|
||||
color: '#a3a3a3',
|
||||
color: colors.textMuted,
|
||||
},
|
||||
right: {
|
||||
alignItems: 'flex-end',
|
||||
@ -192,13 +196,13 @@ const styles = StyleSheet.create({
|
||||
memberCount: {
|
||||
fontSize: 11,
|
||||
fontFamily: 'Nunito_700Bold',
|
||||
color: '#737373',
|
||||
color: colors.textMuted,
|
||||
marginLeft: 3,
|
||||
},
|
||||
time: {
|
||||
fontSize: 10,
|
||||
fontFamily: 'Nunito_500Medium',
|
||||
color: '#a3a3a3',
|
||||
color: colors.textMuted,
|
||||
marginLeft: 'auto',
|
||||
paddingLeft: 6,
|
||||
},
|
||||
@ -206,7 +210,7 @@ const styles = StyleSheet.create({
|
||||
marginLeft: 6,
|
||||
paddingHorizontal: 8,
|
||||
paddingVertical: 3,
|
||||
backgroundColor: '#eff6ff',
|
||||
backgroundColor: colors.surface,
|
||||
borderRadius: 10,
|
||||
},
|
||||
joinBadgeText: {
|
||||
@ -214,4 +218,5 @@ const styles = StyleSheet.create({
|
||||
fontFamily: 'Nunito_700Bold',
|
||||
color: '#007AFF',
|
||||
},
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
@ -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 */}
|
||||
<View style={{ alignItems: 'center', paddingTop: 8, paddingBottom: 4 }}>
|
||||
<View style={{ width: 36, height: 4, borderRadius: 2, backgroundColor: '#d4d4d4' }} />
|
||||
<View style={{ width: 36, height: 4, borderRadius: 2, backgroundColor: colors.border }} />
|
||||
</View>
|
||||
|
||||
{/* 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' ? (
|
||||
<Pressable onPress={handleBack} hitSlop={10}>
|
||||
<Text style={{ fontSize: 16, fontFamily: 'Nunito_400Regular', color: '#525252' }}>
|
||||
<Text style={{ fontSize: 16, fontFamily: 'Nunito_400Regular', color: colors.textMuted }}>
|
||||
{t('common.back')}
|
||||
</Text>
|
||||
</Pressable>
|
||||
) : (
|
||||
<Pressable onPress={handleClose} hitSlop={10}>
|
||||
<Text style={{ fontSize: 16, fontFamily: 'Nunito_400Regular', color: '#525252' }}>
|
||||
<Text style={{ fontSize: 16, fontFamily: 'Nunito_400Regular', color: colors.textMuted }}>
|
||||
{t('common.cancel')}
|
||||
</Text>
|
||||
</Pressable>
|
||||
)}
|
||||
|
||||
<Text style={{ fontSize: 16, fontFamily: 'Nunito_700Bold', color: '#0a0a0a' }}>
|
||||
<Text style={{ fontSize: 16, fontFamily: 'Nunito_700Bold', color: colors.text }}>
|
||||
{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 (
|
||||
<ScrollView
|
||||
style={{ flex: 1 }}
|
||||
@ -303,7 +306,7 @@ function ProviderGrid({
|
||||
style={{
|
||||
fontSize: 13,
|
||||
fontFamily: 'Nunito_400Regular',
|
||||
color: '#737373',
|
||||
color: colors.textMuted,
|
||||
marginBottom: 4,
|
||||
lineHeight: 18,
|
||||
}}
|
||||
@ -325,9 +328,9 @@ function ProviderGrid({
|
||||
flexDirection: 'row',
|
||||
alignItems: 'center',
|
||||
gap: 10,
|
||||
backgroundColor: '#f9f9f9',
|
||||
backgroundColor: colors.surface,
|
||||
borderWidth: 1,
|
||||
borderColor: '#e5e5e5',
|
||||
borderColor: colors.border,
|
||||
borderRadius: 14,
|
||||
padding: 14,
|
||||
}}>
|
||||
@ -345,13 +348,13 @@ function ProviderGrid({
|
||||
</View>
|
||||
<View style={{ flex: 1 }}>
|
||||
<Text
|
||||
style={{ fontSize: 13, fontFamily: 'Nunito_700Bold', color: '#0a0a0a' }}
|
||||
style={{ fontSize: 13, fontFamily: 'Nunito_700Bold', color: colors.text }}
|
||||
numberOfLines={1}
|
||||
>
|
||||
{t(p.labelKey)}
|
||||
</Text>
|
||||
</View>
|
||||
<Ionicons name="chevron-forward" size={14} color="#d4d4d4" />
|
||||
<Ionicons name="chevron-forward" size={14} color={colors.border} />
|
||||
</View>
|
||||
</Pressable>
|
||||
))}
|
||||
@ -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,
|
||||
}}
|
||||
/>
|
||||
</View>
|
||||
@ -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,
|
||||
}}
|
||||
/>
|
||||
<Pressable
|
||||
|
||||
@ -16,6 +16,7 @@ import { useSafeAreaInsets } from 'react-native-safe-area-context';
|
||||
import { Ionicons } from '@expo/vector-icons';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
import { useMailConnect } from '../../hooks/useMailConnect';
|
||||
import { useColors } from '../../lib/theme';
|
||||
|
||||
const SCREEN_HEIGHT = Dimensions.get('window').height;
|
||||
const SHEET_HEIGHT = SCREEN_HEIGHT * 0.5;
|
||||
@ -33,6 +34,7 @@ type Props = {
|
||||
*/
|
||||
export function EditMailAccountSheet({ visible, email, onClose, onSuccess }: Props) {
|
||||
const { t } = useTranslation();
|
||||
const colors = useColors();
|
||||
const insets = useSafeAreaInsets();
|
||||
const { connect, connecting, error: connectError } = useMailConnect();
|
||||
|
||||
@ -104,7 +106,7 @@ export function EditMailAccountSheet({ visible, email, onClose, onSuccess }: Pro
|
||||
right: 0,
|
||||
bottom: 0,
|
||||
height: SHEET_HEIGHT,
|
||||
backgroundColor: '#fff',
|
||||
backgroundColor: colors.bg,
|
||||
borderTopLeftRadius: 20,
|
||||
borderTopRightRadius: 20,
|
||||
transform: [{ translateY }],
|
||||
@ -116,7 +118,7 @@ export function EditMailAccountSheet({ visible, email, onClose, onSuccess }: Pro
|
||||
>
|
||||
{/* Drag-Handle */}
|
||||
<View style={{ alignItems: 'center', paddingTop: 8, paddingBottom: 4 }}>
|
||||
<View style={{ width: 36, height: 4, borderRadius: 2, backgroundColor: '#d4d4d4' }} />
|
||||
<View style={{ width: 36, height: 4, borderRadius: 2, backgroundColor: colors.border }} />
|
||||
</View>
|
||||
|
||||
{/* Header */}
|
||||
@ -129,15 +131,15 @@ export function EditMailAccountSheet({ visible, email, onClose, onSuccess }: Pro
|
||||
paddingTop: 6,
|
||||
paddingBottom: 12,
|
||||
borderBottomWidth: 1,
|
||||
borderBottomColor: '#f0f0f0',
|
||||
borderBottomColor: colors.border,
|
||||
}}
|
||||
>
|
||||
<Pressable onPress={onClose} hitSlop={10}>
|
||||
<Text style={{ fontSize: 16, fontFamily: 'Nunito_400Regular', color: '#525252' }}>
|
||||
<Text style={{ fontSize: 16, fontFamily: 'Nunito_400Regular', color: colors.textMuted }}>
|
||||
{t('common.cancel')}
|
||||
</Text>
|
||||
</Pressable>
|
||||
<Text style={{ fontSize: 16, fontFamily: 'Nunito_700Bold', color: '#0a0a0a' }}>
|
||||
<Text style={{ fontSize: 16, fontFamily: 'Nunito_700Bold', color: colors.text }}>
|
||||
{t('mail.edit_account_title')}
|
||||
</Text>
|
||||
<View style={{ width: 60 }} />
|
||||
@ -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,
|
||||
}}
|
||||
>
|
||||
<Ionicons name="lock-closed-outline" size={16} color="#a3a3a3" />
|
||||
<Ionicons name="lock-closed-outline" size={16} color={colors.textMuted} />
|
||||
<TextInput
|
||||
value={password}
|
||||
onChangeText={(v) => {
|
||||
@ -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,
|
||||
}}
|
||||
/>
|
||||
<Pressable onPress={() => setPasswordVisible((p) => !p)} hitSlop={8}>
|
||||
|
||||
@ -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 (
|
||||
<View
|
||||
style={{
|
||||
backgroundColor: '#fff',
|
||||
backgroundColor: colors.bg,
|
||||
borderRadius: 20,
|
||||
borderWidth: 1,
|
||||
borderColor: '#e5e5e5',
|
||||
borderColor: colors.border,
|
||||
padding: 28,
|
||||
alignItems: 'center',
|
||||
}}
|
||||
@ -45,7 +47,7 @@ export function MailEmptyState({ onConnectPress }: Props) {
|
||||
style={{
|
||||
fontSize: 17,
|
||||
fontFamily: 'Nunito_700Bold',
|
||||
color: '#0a0a0a',
|
||||
color: colors.text,
|
||||
textAlign: 'center',
|
||||
marginBottom: 8,
|
||||
}}
|
||||
@ -57,7 +59,7 @@ export function MailEmptyState({ onConnectPress }: Props) {
|
||||
style={{
|
||||
fontSize: 13,
|
||||
fontFamily: 'Nunito_400Regular',
|
||||
color: '#737373',
|
||||
color: colors.textMuted,
|
||||
textAlign: 'center',
|
||||
lineHeight: 19,
|
||||
marginBottom: 20,
|
||||
@ -71,7 +73,7 @@ export function MailEmptyState({ onConnectPress }: Props) {
|
||||
{(['privacy_1', 'privacy_2', 'privacy_3'] as const).map((key) => (
|
||||
<View key={key} style={{ flexDirection: 'row', alignItems: 'center', gap: 8 }}>
|
||||
<Ionicons name="checkmark-circle" size={15} color="#16a34a" />
|
||||
<Text style={{ fontSize: 12, fontFamily: 'Nunito_400Regular', color: '#525252', flex: 1 }}>
|
||||
<Text style={{ fontSize: 12, fontFamily: 'Nunito_400Regular', color: colors.textMuted, flex: 1 }}>
|
||||
{t(`mail.${key}`)}
|
||||
</Text>
|
||||
</View>
|
||||
|
||||
@ -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 (
|
||||
<View
|
||||
style={{
|
||||
|
||||
@ -2,11 +2,12 @@
|
||||
import { useEffect, useRef, useState } from 'react';
|
||||
import { View, Text, Pressable, Animated, StyleSheet } from 'react-native';
|
||||
import { BREATH_PHASES, TOTAL_ROUNDS, type BreathState } from '../../lib/sosConstants';
|
||||
import { colors } from '../../lib/theme';
|
||||
import { useColors } from '../../lib/theme';
|
||||
|
||||
type Props = { onDone: () => void; onSpeak?: (text: string) => Promise<void> | void };
|
||||
|
||||
export function BreathingCard({ onDone, onSpeak }: Props) {
|
||||
const colors = useColors();
|
||||
const [breathState, setBreathState] = useState<BreathState>('idle');
|
||||
const [countdown, setCountdown] = useState(3);
|
||||
const [round, setRound] = useState(1);
|
||||
@ -86,7 +87,7 @@ export function BreathingCard({ onDone, onSpeak }: Props) {
|
||||
<View style={{ alignItems: 'center', gap: 16 }}>
|
||||
<Text style={st.breathTitle}>4-7-8 Atemübung</Text>
|
||||
<Text style={st.breathSub}>3 Runden · beruhigt dein Nervensystem</Text>
|
||||
<Pressable style={st.breathStartBtn} onPress={() => { setCountdown(3); setBreathState('countdown'); }}>
|
||||
<Pressable style={[st.breathStartBtn, { backgroundColor: colors.brandOrange }]} onPress={() => { setCountdown(3); setBreathState('countdown'); }}>
|
||||
<Text style={st.breathStartTxt}>Starten</Text>
|
||||
</Pressable>
|
||||
</View>
|
||||
@ -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 (
|
||||
<>
|
||||
<View style={st.breathBackdrop} pointerEvents="none" />
|
||||
<Animated.View style={[st.breathDrawerContainer, { transform: [{ translateY: slideAnim }] }]}>
|
||||
<Animated.View style={[st.breathDrawerContainer, { transform: [{ translateY: slideAnim }], backgroundColor: colors.bg }]}>
|
||||
<View style={st.breathDrawerHandle} />
|
||||
<BreathingCard onDone={onDone} onSpeak={onSpeak} />
|
||||
</Animated.View>
|
||||
@ -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 },
|
||||
|
||||
@ -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> | void;
|
||||
onClose: () => void;
|
||||
}) {
|
||||
const colors = useColors();
|
||||
const slide = useRef(new Animated.Value(600)).current;
|
||||
const [better, setBetter] = useState<boolean | null>(null);
|
||||
const [rating, setRating] = useState(0);
|
||||
@ -58,7 +59,7 @@ export function InlineRatingDrawer({
|
||||
return (
|
||||
<>
|
||||
<Pressable style={s.backdrop} onPress={onClose} />
|
||||
<Animated.View style={[s.drawer, { transform: [{ translateY: slide }] }]}>
|
||||
<Animated.View style={[s.drawer, { transform: [{ translateY: slide }], backgroundColor: colors.bg }]}>
|
||||
<KeyboardAvoidingView
|
||||
behavior={Platform.OS === 'ios' ? 'padding' : 'height'}
|
||||
keyboardVerticalOffset={Platform.OS === 'ios' ? 24 : 0}
|
||||
@ -71,13 +72,13 @@ export function InlineRatingDrawer({
|
||||
>
|
||||
<View style={s.header}>
|
||||
<Ionicons name="star" size={20} color="#f59e0b" />
|
||||
<Text style={s.title}>Bewerte diese Session</Text>
|
||||
<Text style={[s.title, { color: colors.text }]}>Bewerte diese Session</Text>
|
||||
</View>
|
||||
<Text style={s.sub}>
|
||||
<Text style={[s.sub, { color: colors.textMuted }]}>
|
||||
Dein Feedback hilft uns, Lyra besser zu machen.
|
||||
</Text>
|
||||
|
||||
<Text style={s.q}>Fühlst du dich besser?</Text>
|
||||
<Text style={[s.q, { color: colors.textMuted }]}>Fühlst du dich besser?</Text>
|
||||
<View style={s.btnRow}>
|
||||
<Pressable
|
||||
style={[s.choiceBtn, better === true && s.choiceBtnYes]}
|
||||
@ -103,7 +104,7 @@ export function InlineRatingDrawer({
|
||||
</Pressable>
|
||||
</View>
|
||||
|
||||
<Text style={s.q}>Bewertung</Text>
|
||||
<Text style={[s.q, { color: colors.textMuted }]}>Bewertung</Text>
|
||||
<View style={s.starsRow}>
|
||||
{[1, 2, 3, 4, 5].map((n) => (
|
||||
<Pressable key={n} onPress={() => setRating(n)} hitSlop={6}>
|
||||
@ -116,9 +117,9 @@ export function InlineRatingDrawer({
|
||||
))}
|
||||
</View>
|
||||
|
||||
<Text style={s.q}>Bemerkung (optional)</Text>
|
||||
<Text style={[s.q, { color: colors.textMuted }]}>Bemerkung (optional)</Text>
|
||||
<TextInput
|
||||
style={s.textArea}
|
||||
style={[s.textArea, { backgroundColor: colors.surfaceElevated, borderColor: colors.border, color: colors.text }]}
|
||||
placeholder="Was war hilfreich? Was nicht?"
|
||||
placeholderTextColor="#94a3b8"
|
||||
multiline
|
||||
@ -132,7 +133,7 @@ export function InlineRatingDrawer({
|
||||
<Text style={s.cancelTxt}>Abbrechen</Text>
|
||||
</Pressable>
|
||||
<Pressable
|
||||
style={[s.submitBtn, submitting && { opacity: 0.6 }]}
|
||||
style={[s.submitBtn, { backgroundColor: colors.brandOrange }, submitting && { opacity: 0.6 }]}
|
||||
onPress={submit}
|
||||
disabled={submitting}
|
||||
>
|
||||
@ -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' },
|
||||
});
|
||||
|
||||
@ -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 (
|
||||
<>
|
||||
<Pressable style={s.backdrop} onPress={onClose} />
|
||||
<Animated.View style={[s.drawer, { transform: [{ translateY: slide }] }]}>
|
||||
<Animated.View style={[s.drawer, { transform: [{ translateY: slide }], backgroundColor: colors.bg }]}>
|
||||
<KeyboardAvoidingView
|
||||
behavior={Platform.OS === 'ios' ? 'padding' : 'height'}
|
||||
keyboardVerticalOffset={Platform.OS === 'ios' ? 24 : 0}
|
||||
@ -80,9 +81,9 @@ export function ShareSuccessDrawer({
|
||||
>
|
||||
<View style={s.header}>
|
||||
<Ionicons name="sparkles" size={20} color={colors.brandOrange} />
|
||||
<Text style={s.title}>Erfolg teilen</Text>
|
||||
<Text style={[s.title, { color: colors.text }]}>Erfolg teilen</Text>
|
||||
</View>
|
||||
<Text style={s.sub}>
|
||||
<Text style={[s.sub, { color: colors.textMuted }]}>
|
||||
Inspiriere andere — dein Beitrag wird anonym in der Community gepostet.
|
||||
</Text>
|
||||
|
||||
@ -93,7 +94,7 @@ export function ShareSuccessDrawer({
|
||||
</View>
|
||||
) : (
|
||||
<TextInput
|
||||
style={s.textArea}
|
||||
style={[s.textArea, { backgroundColor: colors.surfaceElevated, borderColor: colors.border, color: colors.text }]}
|
||||
multiline
|
||||
value={text}
|
||||
onChangeText={setText}
|
||||
@ -118,7 +119,7 @@ export function ShareSuccessDrawer({
|
||||
<Text style={s.cancelTxt}>Abbrechen</Text>
|
||||
</Pressable>
|
||||
<Pressable
|
||||
style={[s.shareBtn, (!text.trim() || submitting || generating) && s.shareBtnDisabled]}
|
||||
style={[s.shareBtn, { backgroundColor: colors.brandOrange }, (!text.trim() || submitting || generating) && s.shareBtnDisabled]}
|
||||
onPress={handleShare}
|
||||
disabled={!text.trim() || submitting || generating}
|
||||
>
|
||||
@ -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 },
|
||||
|
||||
@ -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<boolean | null>(null);
|
||||
const [rating, setRating] = useState<number>(0);
|
||||
const [text, setText] = useState('');
|
||||
@ -43,12 +44,12 @@ export function SosFeedbackModal({
|
||||
keyboardShouldPersistTaps="handled"
|
||||
showsVerticalScrollIndicator={false}
|
||||
>
|
||||
<View style={s.card}>
|
||||
<Text style={s.title}>Wie war diese Session?</Text>
|
||||
<Text style={s.sub}>Dein Feedback hilft Lyra besser zu werden.</Text>
|
||||
<View style={[s.card, { backgroundColor: colors.bg }]}>
|
||||
<Text style={[s.title, { color: colors.text }]}>Wie war diese Session?</Text>
|
||||
<Text style={[s.sub, { color: colors.textMuted }]}>Dein Feedback hilft Lyra besser zu werden.</Text>
|
||||
|
||||
{/* Better Yes/No */}
|
||||
<Text style={s.q}>Fühlst du dich besser?</Text>
|
||||
<Text style={[s.q, { color: colors.textMuted }]}>Fühlst du dich besser?</Text>
|
||||
<View style={s.btnRow}>
|
||||
<Pressable
|
||||
style={[s.choiceBtn, better === true && s.choiceBtnYes]}
|
||||
@ -67,7 +68,7 @@ export function SosFeedbackModal({
|
||||
</View>
|
||||
|
||||
{/* Stars */}
|
||||
<Text style={s.q}>Bewertung</Text>
|
||||
<Text style={[s.q, { color: colors.textMuted }]}>Bewertung</Text>
|
||||
<View style={s.starsRow}>
|
||||
{[1, 2, 3, 4, 5].map((n) => (
|
||||
<Pressable key={n} onPress={() => setRating(n)} hitSlop={6}>
|
||||
@ -81,9 +82,9 @@ export function SosFeedbackModal({
|
||||
</View>
|
||||
|
||||
{/* Comment */}
|
||||
<Text style={s.q}>Bemerkung (optional)</Text>
|
||||
<Text style={[s.q, { color: colors.textMuted }]}>Bemerkung (optional)</Text>
|
||||
<TextInput
|
||||
style={s.textArea}
|
||||
style={[s.textArea, { backgroundColor: colors.surfaceElevated, borderColor: colors.border, color: colors.text }]}
|
||||
placeholder="Was war hilfreich? Was nicht?"
|
||||
placeholderTextColor="#94a3b8"
|
||||
multiline
|
||||
@ -98,7 +99,7 @@ export function SosFeedbackModal({
|
||||
<Pressable style={s.skipBtn} onPress={skip}>
|
||||
<Text style={s.skipTxt}>Überspringen</Text>
|
||||
</Pressable>
|
||||
<Pressable style={s.submitBtn} onPress={submit}>
|
||||
<Pressable style={[s.submitBtn, { backgroundColor: colors.brandOrange }]} onPress={submit}>
|
||||
<Text style={s.submitTxt}>Senden</Text>
|
||||
</Pressable>
|
||||
</View>
|
||||
@ -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' },
|
||||
});
|
||||
|
||||
@ -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 (
|
||||
<View
|
||||
style={{
|
||||
@ -37,8 +38,8 @@ function StatCard({ label, value, color }: { label: string; value: string; color
|
||||
marginHorizontal: 4,
|
||||
borderRadius: 12,
|
||||
borderWidth: 1,
|
||||
borderColor: '#f3f4f6',
|
||||
backgroundColor: '#fafafa',
|
||||
borderColor: colors.border,
|
||||
backgroundColor: colors.surface,
|
||||
paddingVertical: 10,
|
||||
alignItems: 'center',
|
||||
}}
|
||||
@ -50,7 +51,7 @@ function StatCard({ label, value, color }: { label: string; value: string; color
|
||||
textAlign: 'center',
|
||||
fontFamily: 'Nunito_400Regular',
|
||||
fontSize: 11,
|
||||
color: '#6b7280',
|
||||
color: colors.textMuted,
|
||||
}}
|
||||
>
|
||||
{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<UrgeLog[]>([]);
|
||||
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,
|
||||
}}
|
||||
>
|
||||
<Text
|
||||
style={{ fontFamily: 'Nunito_700Bold', fontSize: 15, color: '#111827', marginBottom: 10 }}
|
||||
style={{ fontFamily: 'Nunito_700Bold', fontSize: 15, color: colors.text, marginBottom: 10 }}
|
||||
>
|
||||
{t('urge.this_week')}
|
||||
</Text>
|
||||
<View style={{ flexDirection: 'row', justifyContent: 'space-between' }}>
|
||||
<StatCard label={t('urge.total_urges')} value={String(weeklyStats.total)} color="#111827" />
|
||||
<StatCard label={t('urge.total_urges')} value={String(weeklyStats.total)} color={colors.text} />
|
||||
<StatCard
|
||||
label={t('urge.overcome_count')}
|
||||
value={String(weeklyStats.overcome)}
|
||||
@ -207,8 +209,8 @@ export function UrgeStats() {
|
||||
style={{
|
||||
borderRadius: 18,
|
||||
borderWidth: 1,
|
||||
borderColor: '#e5e7eb',
|
||||
backgroundColor: '#fff',
|
||||
borderColor: colors.border,
|
||||
backgroundColor: colors.bg,
|
||||
padding: 14,
|
||||
flexDirection: 'row',
|
||||
alignItems: 'center',
|
||||
@ -218,7 +220,7 @@ export function UrgeStats() {
|
||||
<Text
|
||||
style={{
|
||||
marginLeft: 8,
|
||||
color: '#374151',
|
||||
color: colors.textMuted,
|
||||
fontFamily: 'Nunito_600SemiBold',
|
||||
flex: 1,
|
||||
}}
|
||||
@ -232,12 +234,12 @@ export function UrgeStats() {
|
||||
style={{
|
||||
borderRadius: 18,
|
||||
borderWidth: 1,
|
||||
borderColor: '#e5e7eb',
|
||||
backgroundColor: '#fff',
|
||||
borderColor: colors.border,
|
||||
backgroundColor: colors.bg,
|
||||
padding: 14,
|
||||
}}
|
||||
>
|
||||
<Text style={{ fontFamily: 'Nunito_700Bold', fontSize: 15, color: '#111827' }}>
|
||||
<Text style={{ fontFamily: 'Nunito_700Bold', fontSize: 15, color: colors.text }}>
|
||||
{t('urge.chart_weekday_title')}
|
||||
</Text>
|
||||
<View style={{ flexDirection: 'row', alignItems: 'flex-end', height: 70, marginTop: 10 }}>
|
||||
@ -254,7 +256,7 @@ export function UrgeStats() {
|
||||
}}
|
||||
/>
|
||||
<Text
|
||||
style={{ fontFamily: 'Nunito_600SemiBold', fontSize: 10, color: '#6b7280' }}
|
||||
style={{ fontFamily: 'Nunito_600SemiBold', fontSize: 10, color: colors.textMuted }}
|
||||
>
|
||||
{day.label}
|
||||
</Text>
|
||||
@ -268,12 +270,12 @@ export function UrgeStats() {
|
||||
style={{
|
||||
borderRadius: 18,
|
||||
borderWidth: 1,
|
||||
borderColor: '#e5e7eb',
|
||||
backgroundColor: '#fff',
|
||||
borderColor: colors.border,
|
||||
backgroundColor: colors.bg,
|
||||
padding: 14,
|
||||
}}
|
||||
>
|
||||
<Text style={{ fontFamily: 'Nunito_700Bold', fontSize: 15, color: '#111827' }}>
|
||||
<Text style={{ fontFamily: 'Nunito_700Bold', fontSize: 15, color: colors.text }}>
|
||||
{t('urge.chart_time_title')}
|
||||
</Text>
|
||||
<View style={{ marginTop: 8, gap: 8 }}>
|
||||
@ -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,
|
||||
}}
|
||||
>
|
||||
<View
|
||||
@ -312,7 +314,7 @@ export function UrgeStats() {
|
||||
width: 24,
|
||||
textAlign: 'right',
|
||||
fontSize: 12,
|
||||
color: '#6b7280',
|
||||
color: colors.textMuted,
|
||||
marginLeft: 6,
|
||||
}}
|
||||
>
|
||||
@ -328,12 +330,12 @@ export function UrgeStats() {
|
||||
style={{
|
||||
borderRadius: 18,
|
||||
borderWidth: 1,
|
||||
borderColor: '#e5e7eb',
|
||||
backgroundColor: '#fff',
|
||||
borderColor: colors.border,
|
||||
backgroundColor: colors.bg,
|
||||
padding: 14,
|
||||
}}
|
||||
>
|
||||
<Text style={{ fontFamily: 'Nunito_700Bold', fontSize: 15, color: '#111827' }}>
|
||||
<Text style={{ fontFamily: 'Nunito_700Bold', fontSize: 15, color: colors.text }}>
|
||||
{t('urge.chart_top_emotions')}
|
||||
</Text>
|
||||
<View style={{ flexDirection: 'row', flexWrap: 'wrap', marginTop: 8 }}>
|
||||
@ -341,9 +343,9 @@ export function UrgeStats() {
|
||||
<View
|
||||
key={emo}
|
||||
style={{
|
||||
backgroundColor: '#f3f4f6',
|
||||
backgroundColor: colors.surfaceElevated,
|
||||
borderWidth: 1,
|
||||
borderColor: '#e5e7eb',
|
||||
borderColor: colors.border,
|
||||
borderRadius: 999,
|
||||
paddingHorizontal: 10,
|
||||
paddingVertical: 6,
|
||||
@ -352,7 +354,7 @@ export function UrgeStats() {
|
||||
}}
|
||||
>
|
||||
<Text
|
||||
style={{ fontSize: 12, color: '#374151', fontFamily: 'Nunito_600SemiBold' }}
|
||||
style={{ fontSize: 12, color: colors.textMuted, fontFamily: 'Nunito_600SemiBold' }}
|
||||
>
|
||||
{emotionLabel(emo, t)} x{c}
|
||||
</Text>
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user