refactor(native): Pressable → TouchableOpacity sweep (style-fn swallows Android styles)

Alle <Pressable style={({pressed}) => ({...})}> ersetzt — style-Funktion
droppt auf Android (New Arch) intermittierend width/height, führt zu 0×0
unsichtbaren Elementen. TouchableOpacity mit activeOpacity ist stabil.

Außerdem übrige Pressables (plain style) aus components/ und app/
migriert sowie zwei überschüssige </View>-Tags in chat.tsx + RoomCard.tsx
entfernt die TS-Fehler verursacht haben.

64 Dateien, typecheck sauber.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
chahinebrini 2026-05-11 15:43:10 +02:00
parent 1ad964f54b
commit 14452b2a46
64 changed files with 615 additions and 638 deletions

View File

@ -4,6 +4,7 @@ import {
Text, Text,
FlatList, FlatList,
Pressable, Pressable,
TouchableOpacity,
ActivityIndicator, ActivityIndicator,
Image, Image,
RefreshControl, RefreshControl,
@ -45,8 +46,7 @@ function DmItem({ conv, onPress }: { conv: DmConversation; onPress: () => void }
return ( return (
<Pressable onPress={onPress} android_ripple={{ color: '#f5f5f5' }}> <Pressable onPress={onPress} android_ripple={{ color: '#f5f5f5' }}>
{({ pressed }) => ( <View style={styles.dmRow}>
<View style={[styles.dmRow, { opacity: pressed ? 0.75 : 1 }]}>
<View style={styles.dmAvatar}> <View style={styles.dmAvatar}>
{conv.partnerAvatar ? ( {conv.partnerAvatar ? (
<Image source={{ uri: conv.partnerAvatar }} style={styles.dmAvatarImg} /> <Image source={{ uri: conv.partnerAvatar }} style={styles.dmAvatarImg} />
@ -88,8 +88,7 @@ function DmItem({ conv, onPress }: { conv: DmConversation; onPress: () => void }
)} )}
</View> </View>
</View> </View>
</View> </View>
)}
</Pressable> </Pressable>
); );
} }
@ -150,19 +149,21 @@ export default function ChatScreen() {
<View style={styles.titleRow}> <View style={styles.titleRow}>
<Text style={styles.title}>{t('chat.title')}</Text> <Text style={styles.title}>{t('chat.title')}</Text>
{tab === 'groups' && ( {tab === 'groups' && (
<Pressable <TouchableOpacity
onPress={() => setCreateOpen(true)} onPress={() => setCreateOpen(true)}
style={({ pressed }) => [styles.createBtn, { opacity: pressed ? 0.7 : 1 }]} activeOpacity={0.7}
style={styles.createBtn}
> >
<Ionicons name="add" size={20} color="#fff" /> <Ionicons name="add" size={20} color="#fff" />
</Pressable> </TouchableOpacity>
)} )}
</View> </View>
{/* Tabs */} {/* Tabs */}
<View style={styles.tabs}> <View style={styles.tabs}>
<Pressable <TouchableOpacity
onPress={() => setTab('groups')} onPress={() => setTab('groups')}
activeOpacity={0.7}
style={[styles.tab, tab === 'groups' && styles.tabActive]} style={[styles.tab, tab === 'groups' && styles.tabActive]}
> >
<Ionicons <Ionicons
@ -173,9 +174,10 @@ export default function ChatScreen() {
<Text style={[styles.tabText, tab === 'groups' && styles.tabTextActive]}> <Text style={[styles.tabText, tab === 'groups' && styles.tabTextActive]}>
{t('chat.groups')} {t('chat.groups')}
</Text> </Text>
</Pressable> </TouchableOpacity>
<Pressable <TouchableOpacity
onPress={() => setTab('direct')} onPress={() => setTab('direct')}
activeOpacity={0.7}
style={[styles.tab, tab === 'direct' && styles.tabActive]} style={[styles.tab, tab === 'direct' && styles.tabActive]}
> >
<Ionicons <Ionicons
@ -191,7 +193,7 @@ export default function ChatScreen() {
<Text style={styles.tabBadgeText}>{unreadDms}</Text> <Text style={styles.tabBadgeText}>{unreadDms}</Text>
</View> </View>
)} )}
</Pressable> </TouchableOpacity>
</View> </View>
</View> </View>

View File

@ -4,7 +4,7 @@ import {
Text, Text,
ScrollView, ScrollView,
FlatList, FlatList,
Pressable, TouchableOpacity,
RefreshControl, RefreshControl,
ActivityIndicator, ActivityIndicator,
} from 'react-native'; } from 'react-native';
@ -110,10 +110,10 @@ export default function HomeScreen() {
{/* Filter toggle */} {/* Filter toggle */}
<View className="mb-3"> <View className="mb-3">
<Pressable <TouchableOpacity
onPress={() => setFilterOpen((o) => !o)} onPress={() => setFilterOpen((o) => !o)}
activeOpacity={0.6}
className="flex-row items-center gap-1.5 self-start" className="flex-row items-center gap-1.5 self-start"
style={({ pressed }) => ({ opacity: pressed ? 0.6 : 1 })}
> >
<Ionicons <Ionicons
name={filterOpen ? 'close-outline' : 'options-outline'} name={filterOpen ? 'close-outline' : 'options-outline'}
@ -125,7 +125,7 @@ export default function HomeScreen() {
{FILTERS.find((f) => f.value === activeCategory)?.label} {FILTERS.find((f) => f.value === activeCategory)?.label}
</Text> </Text>
)} )}
</Pressable> </TouchableOpacity>
{filterOpen && ( {filterOpen && (
<ScrollView <ScrollView
@ -137,11 +137,11 @@ export default function HomeScreen() {
{FILTERS.map((f) => { {FILTERS.map((f) => {
const active = activeCategory === f.value; const active = activeCategory === f.value;
return ( return (
<Pressable <TouchableOpacity
key={f.value} key={f.value}
onPress={() => toggleFilter(f.value)} onPress={() => toggleFilter(f.value)}
style={({ pressed }) => ({ activeOpacity={0.7}
opacity: pressed ? 0.7 : 1, style={{
flexDirection: 'row', flexDirection: 'row',
alignItems: 'center', alignItems: 'center',
gap: 6, gap: 6,
@ -151,7 +151,7 @@ export default function HomeScreen() {
borderWidth: 1, borderWidth: 1,
backgroundColor: active ? colors.brandOrange : colors.surface, backgroundColor: active ? colors.brandOrange : colors.surface,
borderColor: active ? colors.brandOrange : colors.border, borderColor: active ? colors.brandOrange : colors.border,
})} }}
> >
<Ionicons <Ionicons
name={f.icon} name={f.icon}
@ -167,7 +167,7 @@ export default function HomeScreen() {
> >
{f.label} {f.label}
</Text> </Text>
</Pressable> </TouchableOpacity>
); );
})} })}
</ScrollView> </ScrollView>

View File

@ -1,5 +1,5 @@
import { useEffect } from 'react'; import { useEffect } from 'react';
import { View, Text, FlatList, Pressable, RefreshControl } from 'react-native'; import { View, Text, FlatList, TouchableOpacity, RefreshControl } from 'react-native';
import { SafeAreaView } from 'react-native-safe-area-context'; import { SafeAreaView } from 'react-native-safe-area-context';
import { useRouter } from 'expo-router'; import { useRouter } from 'expo-router';
import { Ionicons } from '@expo/vector-icons'; import { Ionicons } from '@expo/vector-icons';
@ -31,12 +31,13 @@ export default function NotificationsScreen() {
return ( return (
<SafeAreaView style={{ flex: 1, backgroundColor: colors.bg }} edges={['top']}> <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 }}> <View style={{ flexDirection: 'row', alignItems: 'center', gap: 12, paddingHorizontal: 20, paddingTop: 12, paddingBottom: 12, borderBottomWidth: 1, borderBottomColor: colors.border }}>
<Pressable <TouchableOpacity
onPress={() => router.back()} onPress={() => router.back()}
activeOpacity={0.7}
style={{ width: 36, height: 36, borderRadius: 18, backgroundColor: colors.surfaceElevated, borderWidth: 1, borderColor: colors.border, alignItems: 'center', justifyContent: '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={colors.textMuted} /> <Ionicons name="arrow-back" size={18} color={colors.textMuted} />
</Pressable> </TouchableOpacity>
<Text <Text
style={{ color: colors.text, fontSize: 18, flex: 1, fontFamily: 'Nunito_700Bold' }} style={{ color: colors.text, fontSize: 18, flex: 1, fontFamily: 'Nunito_700Bold' }}
> >
@ -91,11 +92,9 @@ function NotificationRow({
const colors = useColors(); const colors = useColors();
const isUnread = !notif.readAt; const isUnread = !notif.readAt;
return ( return (
<Pressable <TouchableOpacity
onPress={onPress} onPress={onPress}
style={({ pressed }) => ({ activeOpacity={0.7}
opacity: pressed ? 0.7 : 1,
})}
> >
<View <View
style={{ style={{
@ -137,11 +136,11 @@ function NotificationRow({
</Text> </Text>
)} )}
</View> </View>
<Pressable onPress={onDelete} hitSlop={8}> <TouchableOpacity onPress={onDelete} hitSlop={8} activeOpacity={0.7}>
<Ionicons name="close" size={16} color="#a3a3a3" /> <Ionicons name="close" size={16} color="#a3a3a3" />
</Pressable> </TouchableOpacity>
</View> </View>
</Pressable> </TouchableOpacity>
); );
} }

View File

@ -3,7 +3,7 @@ import {
View, View,
Text, Text,
TextInput, TextInput,
Pressable, TouchableOpacity,
KeyboardAvoidingView, KeyboardAvoidingView,
Platform, Platform,
ActivityIndicator, ActivityIndicator,
@ -168,10 +168,11 @@ export default function ConfirmOtpScreen() {
</View> </View>
)} )}
<Pressable <TouchableOpacity
onPress={verify} onPress={verify}
disabled={otp.length < OTP_LENGTH || loading || success} disabled={otp.length < OTP_LENGTH || loading || success}
className="bg-rebreak-500 rounded-xl items-center mb-4 active:opacity-80 disabled:opacity-40" activeOpacity={0.8}
className="bg-rebreak-500 rounded-xl items-center mb-4 disabled:opacity-40"
style={{ paddingVertical: 16 }} style={{ paddingVertical: 16 }}
> >
{loading ? ( {loading ? (
@ -179,11 +180,12 @@ export default function ConfirmOtpScreen() {
) : ( ) : (
<Text className="text-white text-base" style={{ fontFamily: 'Nunito_600SemiBold' }}>{t('auth.confirmBtn')}</Text> <Text className="text-white text-base" style={{ fontFamily: 'Nunito_600SemiBold' }}>{t('auth.confirmBtn')}</Text>
)} )}
</Pressable> </TouchableOpacity>
<Pressable <TouchableOpacity
onPress={resend} onPress={resend}
disabled={resendCooldown > 0 || loading} disabled={resendCooldown > 0 || loading}
activeOpacity={0.7}
className="py-3 items-center" className="py-3 items-center"
> >
<Text className="text-neutral-500 text-sm" style={{ fontFamily: 'Nunito_400Regular' }}> <Text className="text-neutral-500 text-sm" style={{ fontFamily: 'Nunito_400Regular' }}>
@ -192,14 +194,15 @@ export default function ConfirmOtpScreen() {
{resendCooldown > 0 ? t('auth.resendCooldown', { seconds: resendCooldown }) : t('auth.resend')} {resendCooldown > 0 ? t('auth.resendCooldown', { seconds: resendCooldown }) : t('auth.resend')}
</Text> </Text>
</Text> </Text>
</Pressable> </TouchableOpacity>
<Pressable <TouchableOpacity
onPress={() => router.back()} onPress={() => router.back()}
activeOpacity={0.7}
className="py-3 items-center mt-2" className="py-3 items-center mt-2"
> >
<Text className="text-neutral-400 text-sm" style={{ fontFamily: 'Nunito_400Regular' }}>{t('auth.backToSignup')}</Text> <Text className="text-neutral-400 text-sm" style={{ fontFamily: 'Nunito_400Regular' }}>{t('auth.backToSignup')}</Text>
</Pressable> </TouchableOpacity>
</View> </View>
</KeyboardAvoidingView> </KeyboardAvoidingView>
</SafeAreaView> </SafeAreaView>

View File

@ -1,5 +1,5 @@
import { useEffect, useState } from 'react'; import { useEffect, useState } from 'react';
import { View, Text, Pressable, ActivityIndicator } from 'react-native'; import { View, Text, TouchableOpacity, ActivityIndicator } from 'react-native';
import { useRouter, useLocalSearchParams } from 'expo-router'; import { useRouter, useLocalSearchParams } from 'expo-router';
import { SafeAreaView } from 'react-native-safe-area-context'; import { SafeAreaView } from 'react-native-safe-area-context';
import { useTranslation } from 'react-i18next'; import { useTranslation } from 'react-i18next';
@ -82,12 +82,13 @@ export default function ConfirmScreen() {
) : ( ) : (
<> <>
<Text className="text-red-600 text-sm text-center mb-6" style={{ fontFamily: 'Nunito_400Regular' }}>{errorMsg}</Text> <Text className="text-red-600 text-sm text-center mb-6" style={{ fontFamily: 'Nunito_400Regular' }}>{errorMsg}</Text>
<Pressable <TouchableOpacity
onPress={() => router.replace('/signin')} onPress={() => router.replace('/signin')}
activeOpacity={0.7}
className="bg-neutral-100 border border-neutral-200 px-6 py-3 rounded-xl" className="bg-neutral-100 border border-neutral-200 px-6 py-3 rounded-xl"
> >
<Text className="text-neutral-800 text-sm" style={{ fontFamily: 'Nunito_600SemiBold' }}>{t('auth.backToLoginPlain')}</Text> <Text className="text-neutral-800 text-sm" style={{ fontFamily: 'Nunito_600SemiBold' }}>{t('auth.backToLoginPlain')}</Text>
</Pressable> </TouchableOpacity>
</> </>
)} )}
</View> </View>

View File

@ -1,4 +1,4 @@
import { View, Text, Pressable } from 'react-native'; import { View, Text, TouchableOpacity } from 'react-native';
import { useRouter } from 'expo-router'; import { useRouter } from 'expo-router';
import { SafeAreaView } from 'react-native-safe-area-context'; import { SafeAreaView } from 'react-native-safe-area-context';
import { useTranslation } from 'react-i18next'; import { useTranslation } from 'react-i18next';
@ -25,19 +25,21 @@ export default function DeviceLimitScreen() {
{/* TODO Phase 4: device management — list active devices + revoke button */} {/* TODO Phase 4: device management — list active devices + revoke button */}
<Pressable <TouchableOpacity
onPress={() => router.replace('/signin')} onPress={() => router.replace('/signin')}
className="bg-rebreak-500 px-8 py-4 rounded-xl active:opacity-80" activeOpacity={0.8}
className="bg-rebreak-500 px-8 py-4 rounded-xl"
> >
<Text className="text-white text-base" style={{ fontFamily: 'Nunito_600SemiBold' }}>{t('auth.toLogin')}</Text> <Text className="text-white text-base" style={{ fontFamily: 'Nunito_600SemiBold' }}>{t('auth.toLogin')}</Text>
</Pressable> </TouchableOpacity>
<Pressable <TouchableOpacity
onPress={() => router.push('/signin')} onPress={() => router.push('/signin')}
activeOpacity={0.7}
className="py-3 mt-2" className="py-3 mt-2"
> >
<Text className="text-neutral-500 text-sm" style={{ fontFamily: 'Nunito_400Regular' }}>{t('auth.deviceLimitUpgrade')}</Text> <Text className="text-neutral-500 text-sm" style={{ fontFamily: 'Nunito_400Regular' }}>{t('auth.deviceLimitUpgrade')}</Text>
</Pressable> </TouchableOpacity>
</View> </View>
</SafeAreaView> </SafeAreaView>
); );

View File

@ -3,7 +3,7 @@ import {
View, View,
Text, Text,
TextInput, TextInput,
Pressable, TouchableOpacity,
KeyboardAvoidingView, KeyboardAvoidingView,
Platform, Platform,
ActivityIndicator, ActivityIndicator,
@ -78,10 +78,11 @@ export default function ForgotPasswordScreen() {
</View> </View>
)} )}
<Pressable <TouchableOpacity
onPress={onSubmit} onPress={onSubmit}
disabled={loading || !email.trim()} disabled={loading || !email.trim()}
className="bg-rebreak-500 rounded-xl items-center mt-1 active:opacity-80 disabled:opacity-40" activeOpacity={0.8}
className="bg-rebreak-500 rounded-xl items-center mt-1 disabled:opacity-40"
style={{ paddingVertical: 16 }} style={{ paddingVertical: 16 }}
> >
{loading ? ( {loading ? (
@ -89,7 +90,7 @@ export default function ForgotPasswordScreen() {
) : ( ) : (
<Text className="text-white text-base" style={{ fontFamily: 'Nunito_600SemiBold' }}>{t('auth.resetPasswordSend')}</Text> <Text className="text-white text-base" style={{ fontFamily: 'Nunito_600SemiBold' }}>{t('auth.resetPasswordSend')}</Text>
)} )}
</Pressable> </TouchableOpacity>
</> </>
) : ( ) : (
<View className="bg-green-50 border border-green-200 rounded-xl px-5 py-6 mb-4"> <View className="bg-green-50 border border-green-200 rounded-xl px-5 py-6 mb-4">
@ -100,12 +101,13 @@ export default function ForgotPasswordScreen() {
</View> </View>
)} )}
<Pressable <TouchableOpacity
onPress={() => router.back()} onPress={() => router.back()}
activeOpacity={0.7}
className="py-4 items-center mt-2" className="py-4 items-center mt-2"
> >
<Text className="text-neutral-500 text-sm" style={{ fontFamily: 'Nunito_400Regular' }}>{t('auth.backToLogin')}</Text> <Text className="text-neutral-500 text-sm" style={{ fontFamily: 'Nunito_400Regular' }}>{t('auth.backToLogin')}</Text>
</Pressable> </TouchableOpacity>
</View> </View>
</KeyboardAvoidingView> </KeyboardAvoidingView>
</SafeAreaView> </SafeAreaView>

View File

@ -3,7 +3,7 @@ import {
View, View,
Text, Text,
TextInput, TextInput,
Pressable, TouchableOpacity,
KeyboardAvoidingView, KeyboardAvoidingView,
Platform, Platform,
ScrollView, ScrollView,
@ -100,10 +100,11 @@ export default function SignInScreen() {
</Text> </Text>
{/* OAuth Buttons */} {/* OAuth Buttons */}
<Pressable <TouchableOpacity
onPress={() => onOAuth('google')} onPress={() => onOAuth('google')}
disabled={isLoading} disabled={isLoading}
className="flex-row items-center justify-center gap-3 bg-white border border-neutral-200 rounded-xl mb-3 active:opacity-80 disabled:opacity-40" activeOpacity={0.8}
className="flex-row items-center justify-center gap-3 bg-white border border-neutral-200 rounded-xl mb-3 disabled:opacity-40"
style={{ paddingVertical: 14 }} style={{ paddingVertical: 14 }}
> >
{oauthLoading === 'google' ? ( {oauthLoading === 'google' ? (
@ -112,12 +113,13 @@ export default function SignInScreen() {
<GoogleIcon /> <GoogleIcon />
)} )}
<Text className="text-neutral-900 text-base" style={{ fontFamily: 'Nunito_600SemiBold' }}>{t('auth.googleSignin')}</Text> <Text className="text-neutral-900 text-base" style={{ fontFamily: 'Nunito_600SemiBold' }}>{t('auth.googleSignin')}</Text>
</Pressable> </TouchableOpacity>
<Pressable <TouchableOpacity
onPress={() => onOAuth('apple')} onPress={() => onOAuth('apple')}
disabled={isLoading} disabled={isLoading}
className="flex-row items-center justify-center gap-3 bg-neutral-900 rounded-xl mb-6 active:opacity-80 disabled:opacity-40" activeOpacity={0.8}
className="flex-row items-center justify-center gap-3 bg-neutral-900 rounded-xl mb-6 disabled:opacity-40"
style={{ paddingVertical: 14 }} style={{ paddingVertical: 14 }}
> >
{oauthLoading === 'apple' ? ( {oauthLoading === 'apple' ? (
@ -126,7 +128,7 @@ export default function SignInScreen() {
<AppleIcon /> <AppleIcon />
)} )}
<Text className="text-white text-base" style={{ fontFamily: 'Nunito_600SemiBold' }}>{t('auth.appleSignin')}</Text> <Text className="text-white text-base" style={{ fontFamily: 'Nunito_600SemiBold' }}>{t('auth.appleSignin')}</Text>
</Pressable> </TouchableOpacity>
{/* Divider */} {/* Divider */}
<View className="flex-row items-center mb-6"> <View className="flex-row items-center mb-6">
@ -159,21 +161,23 @@ export default function SignInScreen() {
onChangeText={setPassword} onChangeText={setPassword}
/> />
<Pressable <TouchableOpacity
onPress={() => router.push('/forgot-password')} onPress={() => router.push('/forgot-password')}
activeOpacity={0.7}
className="self-end py-2 mb-4" className="self-end py-2 mb-4"
> >
<Text className="text-rebreak-500 text-sm" style={{ fontFamily: 'Nunito_400Regular' }}>{t('auth.forgotPassword')}</Text> <Text className="text-rebreak-500 text-sm" style={{ fontFamily: 'Nunito_400Regular' }}>{t('auth.forgotPassword')}</Text>
</Pressable> </TouchableOpacity>
{error && ( {error && (
<Text className="text-red-500 text-sm mb-3" style={{ fontFamily: 'Nunito_400Regular' }}>{error}</Text> <Text className="text-red-500 text-sm mb-3" style={{ fontFamily: 'Nunito_400Regular' }}>{error}</Text>
)} )}
<Pressable <TouchableOpacity
onPress={onSubmit} onPress={onSubmit}
disabled={isLoading || !email || !password} disabled={isLoading || !email || !password}
className="bg-rebreak-500 rounded-xl items-center mt-1 active:opacity-80 disabled:opacity-40" activeOpacity={0.8}
className="bg-rebreak-500 rounded-xl items-center mt-1 disabled:opacity-40"
style={{ paddingVertical: 16 }} style={{ paddingVertical: 16 }}
> >
{submitting ? ( {submitting ? (
@ -181,17 +185,18 @@ export default function SignInScreen() {
) : ( ) : (
<Text className="text-white text-base" style={{ fontFamily: 'Nunito_600SemiBold' }}>{t('auth.signin')}</Text> <Text className="text-white text-base" style={{ fontFamily: 'Nunito_600SemiBold' }}>{t('auth.signin')}</Text>
)} )}
</Pressable> </TouchableOpacity>
<Pressable <TouchableOpacity
onPress={() => router.push('/signup')} onPress={() => router.push('/signup')}
activeOpacity={0.7}
className="py-4 items-center mt-2" className="py-4 items-center mt-2"
> >
<Text className="text-neutral-500 text-sm" style={{ fontFamily: 'Nunito_400Regular' }}> <Text className="text-neutral-500 text-sm" style={{ fontFamily: 'Nunito_400Regular' }}>
{t('auth.noAccount')}{' '} {t('auth.noAccount')}{' '}
<Text className="text-rebreak-500" style={{ fontFamily: 'Nunito_600SemiBold' }}>{t('auth.signup')}</Text> <Text className="text-rebreak-500" style={{ fontFamily: 'Nunito_600SemiBold' }}>{t('auth.signup')}</Text>
</Text> </Text>
</Pressable> </TouchableOpacity>
</ScrollView> </ScrollView>
</KeyboardAvoidingView> </KeyboardAvoidingView>
</SafeAreaView> </SafeAreaView>

View File

@ -3,7 +3,7 @@ import {
View, View,
Text, Text,
TextInput, TextInput,
Pressable, TouchableOpacity,
KeyboardAvoidingView, KeyboardAvoidingView,
Platform, Platform,
ScrollView, ScrollView,
@ -124,10 +124,11 @@ export default function SignUpScreen() {
</Text> </Text>
{/* OAuth Buttons */} {/* OAuth Buttons */}
<Pressable <TouchableOpacity
onPress={() => onOAuth('google')} onPress={() => onOAuth('google')}
disabled={isLoading} disabled={isLoading}
className="flex-row items-center justify-center gap-3 bg-white border border-neutral-200 rounded-xl mb-3 active:opacity-80 disabled:opacity-40" activeOpacity={0.8}
className="flex-row items-center justify-center gap-3 bg-white border border-neutral-200 rounded-xl mb-3 disabled:opacity-40"
style={{ paddingVertical: 14 }} style={{ paddingVertical: 14 }}
> >
{oauthLoading === 'google' ? ( {oauthLoading === 'google' ? (
@ -136,12 +137,13 @@ export default function SignUpScreen() {
<GoogleIcon /> <GoogleIcon />
)} )}
<Text className="text-neutral-900 text-base" style={{ fontFamily: 'Nunito_600SemiBold' }}>{t('auth.googleSignup')}</Text> <Text className="text-neutral-900 text-base" style={{ fontFamily: 'Nunito_600SemiBold' }}>{t('auth.googleSignup')}</Text>
</Pressable> </TouchableOpacity>
<Pressable <TouchableOpacity
onPress={() => onOAuth('apple')} onPress={() => onOAuth('apple')}
disabled={isLoading} disabled={isLoading}
className="flex-row items-center justify-center gap-3 bg-neutral-900 rounded-xl mb-6 active:opacity-80 disabled:opacity-40" activeOpacity={0.8}
className="flex-row items-center justify-center gap-3 bg-neutral-900 rounded-xl mb-6 disabled:opacity-40"
style={{ paddingVertical: 14 }} style={{ paddingVertical: 14 }}
> >
{oauthLoading === 'apple' ? ( {oauthLoading === 'apple' ? (
@ -150,7 +152,7 @@ export default function SignUpScreen() {
<AppleIcon /> <AppleIcon />
)} )}
<Text className="text-white text-base" style={{ fontFamily: 'Nunito_600SemiBold' }}>{t('auth.appleSignup')}</Text> <Text className="text-white text-base" style={{ fontFamily: 'Nunito_600SemiBold' }}>{t('auth.appleSignup')}</Text>
</Pressable> </TouchableOpacity>
{/* Divider */} {/* Divider */}
<View className="flex-row items-center mb-6"> <View className="flex-row items-center mb-6">
@ -226,10 +228,11 @@ export default function SignUpScreen() {
{HERO_AVATARS.map((avatar) => { {HERO_AVATARS.map((avatar) => {
const selected = avatar.id === avatarId; const selected = avatar.id === avatarId;
return ( return (
<Pressable <TouchableOpacity
key={avatar.id} key={avatar.id}
onPress={() => setAvatarId(avatar.id)} onPress={() => setAvatarId(avatar.id)}
disabled={isLoading} disabled={isLoading}
activeOpacity={0.7}
className={`rounded-full ${selected ? 'opacity-100' : 'opacity-40'}`} className={`rounded-full ${selected ? 'opacity-100' : 'opacity-40'}`}
> >
<Image <Image
@ -237,7 +240,7 @@ export default function SignUpScreen() {
className={`w-14 h-14 rounded-full border-2 ${selected ? avatar.color : 'border-transparent'}`} className={`w-14 h-14 rounded-full border-2 ${selected ? avatar.color : 'border-transparent'}`}
style={{ width: 56, height: 56, borderRadius: 28 }} style={{ width: 56, height: 56, borderRadius: 28 }}
/> />
</Pressable> </TouchableOpacity>
); );
})} })}
</View> </View>
@ -251,9 +254,10 @@ export default function SignUpScreen() {
</View> </View>
{/* Terms Checkbox */} {/* Terms Checkbox */}
<Pressable <TouchableOpacity
onPress={() => setTermsAccepted(!termsAccepted)} onPress={() => setTermsAccepted(!termsAccepted)}
disabled={isLoading} disabled={isLoading}
activeOpacity={0.7}
className="flex-row items-start gap-3 mb-6" className="flex-row items-start gap-3 mb-6"
> >
<View <View
@ -270,12 +274,13 @@ export default function SignUpScreen() {
<Text className="text-rebreak-500" style={{ fontFamily: 'Nunito_600SemiBold' }}>{t('auth.termsLink')}</Text> <Text className="text-rebreak-500" style={{ fontFamily: 'Nunito_600SemiBold' }}>{t('auth.termsLink')}</Text>
{t('auth.acceptTermsSuffix')} {t('auth.acceptTermsSuffix')}
</Text> </Text>
</Pressable> </TouchableOpacity>
<Pressable <TouchableOpacity
onPress={onSubmit} onPress={onSubmit}
disabled={isLoading || !email || !password || !nickname || !termsAccepted} disabled={isLoading || !email || !password || !nickname || !termsAccepted}
className="bg-rebreak-500 rounded-xl items-center active:opacity-80 disabled:opacity-40" activeOpacity={0.8}
className="bg-rebreak-500 rounded-xl items-center disabled:opacity-40"
style={{ paddingVertical: 16 }} style={{ paddingVertical: 16 }}
> >
{submitting ? ( {submitting ? (
@ -283,17 +288,18 @@ export default function SignUpScreen() {
) : ( ) : (
<Text className="text-white text-base" style={{ fontFamily: 'Nunito_600SemiBold' }}>{t('auth.signupTitle')}</Text> <Text className="text-white text-base" style={{ fontFamily: 'Nunito_600SemiBold' }}>{t('auth.signupTitle')}</Text>
)} )}
</Pressable> </TouchableOpacity>
<Pressable <TouchableOpacity
onPress={() => router.push('/signin')} onPress={() => router.push('/signin')}
activeOpacity={0.7}
className="py-4 items-center mt-2" className="py-4 items-center mt-2"
> >
<Text className="text-neutral-500 text-sm" style={{ fontFamily: 'Nunito_400Regular' }}> <Text className="text-neutral-500 text-sm" style={{ fontFamily: 'Nunito_400Regular' }}>
{t('auth.alreadyRegistered')}{' '} {t('auth.alreadyRegistered')}{' '}
<Text className="text-rebreak-500" style={{ fontFamily: 'Nunito_600SemiBold' }}>{t('auth.signin')}</Text> <Text className="text-rebreak-500" style={{ fontFamily: 'Nunito_600SemiBold' }}>{t('auth.signin')}</Text>
</Text> </Text>
</Pressable> </TouchableOpacity>
</ScrollView> </ScrollView>
</KeyboardAvoidingView> </KeyboardAvoidingView>
</SafeAreaView> </SafeAreaView>

View File

@ -1,5 +1,5 @@
import { useEffect, useState } from 'react'; import { useEffect, useState } from 'react';
import { View, Text, ScrollView, Pressable, Alert } from 'react-native'; import { View, Text, ScrollView, TouchableOpacity, Alert } from 'react-native';
import { SafeAreaView } from 'react-native-safe-area-context'; import { SafeAreaView } from 'react-native-safe-area-context';
import { useRouter } from 'expo-router'; import { useRouter } from 'expo-router';
import { Ionicons } from '@expo/vector-icons'; import { Ionicons } from '@expo/vector-icons';
@ -36,22 +36,14 @@ export default function DebugScreen() {
borderBottomColor: 'rgba(0,0,0,0.06)', borderBottomColor: 'rgba(0,0,0,0.06)',
}} }}
> >
<Pressable <TouchableOpacity
onPress={() => router.back()} onPress={() => router.back()}
hitSlop={8} hitSlop={8}
style={({ pressed }) => ({ activeOpacity={0.6}
opacity: pressed ? 0.6 : 1, style={{ width: 40, height: 40, alignItems: 'center', justifyContent: 'center' }}
})}
> >
<View style={{ <Ionicons name="chevron-back" size={26} color={colors.text} />
width: 40, </TouchableOpacity>
height: 40,
alignItems: 'center',
justifyContent: 'center',
}}>
<Ionicons name="chevron-back" size={26} color={colors.text} />
</View>
</Pressable>
<Text style={{ fontSize: 20, color: colors.text, fontFamily: 'Nunito_700Bold' }}> <Text style={{ fontSize: 20, color: colors.text, fontFamily: 'Nunito_700Bold' }}>
Debug Debug
</Text> </Text>
@ -214,18 +206,19 @@ function PlanOverrideToggle({
const isActive = plan === currentPlan; const isActive = plan === currentPlan;
const accent = PLAN_COLOR[plan]; const accent = PLAN_COLOR[plan];
return ( return (
<Pressable <TouchableOpacity
key={plan} key={plan}
onPress={() => switchPlan(plan)} onPress={() => switchPlan(plan)}
disabled={loading || isActive} disabled={loading || isActive}
style={({ pressed }) => ({ activeOpacity={0.7}
style={{
flex: 1, flex: 1,
paddingVertical: 10, paddingVertical: 10,
borderRadius: 10, borderRadius: 10,
alignItems: 'center', alignItems: 'center',
backgroundColor: isActive ? accent : colors.surfaceElevated, backgroundColor: isActive ? accent : colors.surfaceElevated,
opacity: loading ? 0.5 : pressed ? 0.7 : 1, opacity: loading ? 0.5 : 1,
})} }}
> >
<Text <Text
style={{ style={{
@ -237,7 +230,7 @@ function PlanOverrideToggle({
> >
{plan} {plan}
</Text> </Text>
</Pressable> </TouchableOpacity>
); );
})} })}
</View> </View>

View File

@ -2,7 +2,6 @@ import {
ActivityIndicator, ActivityIndicator,
Alert, Alert,
Platform, Platform,
Pressable,
ScrollView, ScrollView,
Text, Text,
TouchableOpacity, TouchableOpacity,
@ -222,13 +221,13 @@ function MobileDeviceRow({
</View> </View>
{!device.isCurrent ? ( {!device.isCurrent ? (
<Pressable <TouchableOpacity
onPress={confirmRemove} onPress={confirmRemove}
hitSlop={8} hitSlop={8}
style={({ pressed }) => ({ opacity: pressed ? 0.5 : 1 })} activeOpacity={0.5}
> >
<Ionicons name="trash-outline" size={18} color={colors.error} /> <Ionicons name="trash-outline" size={18} color={colors.error} />
</Pressable> </TouchableOpacity>
) : null} ) : null}
</View> </View>
); );
@ -324,12 +323,12 @@ function ProtectedDeviceRow({
onPressAction={({ nativeEvent: { event } }) => handleMenuSelect(event)} onPressAction={({ nativeEvent: { event } }) => handleMenuSelect(event)}
shouldOpenOnLongPress={false} shouldOpenOnLongPress={false}
> >
<Pressable <TouchableOpacity
hitSlop={8} hitSlop={8}
style={({ pressed }) => ({ opacity: pressed ? 0.5 : 1 })} activeOpacity={0.5}
> >
<Ionicons name="ellipsis-horizontal" size={18} color={colors.textMuted} /> <Ionicons name="ellipsis-horizontal" size={18} color={colors.textMuted} />
</Pressable> </TouchableOpacity>
</MenuView> </MenuView>
</View> </View>
); );
@ -579,19 +578,19 @@ export default function DevicesScreen() {
{t('devices.subtitle_legend')} {t('devices.subtitle_legend')}
</Text> </Text>
</View> </View>
<Pressable <TouchableOpacity
style={({ pressed }) => ({ activeOpacity={0.7}
style={{
backgroundColor: colors.brandOrange, backgroundColor: colors.brandOrange,
borderRadius: 12, borderRadius: 12,
paddingVertical: 14, paddingVertical: 14,
alignItems: 'center', alignItems: 'center',
opacity: pressed ? 0.7 : 1, }}
})}
> >
<Text style={{ fontSize: 15, color: '#fff', fontFamily: 'Nunito_700Bold' }}> <Text style={{ fontSize: 15, color: '#fff', fontFamily: 'Nunito_700Bold' }}>
{t('devices.upgrade_cta')} {t('devices.upgrade_cta')}
</Text> </Text>
</Pressable> </TouchableOpacity>
</View> </View>
)} )}

View File

@ -3,7 +3,7 @@ import {
View, View,
Text, Text,
FlatList, FlatList,
Pressable, TouchableOpacity,
KeyboardAvoidingView, KeyboardAvoidingView,
Platform, Platform,
ActivityIndicator, ActivityIndicator,
@ -236,9 +236,9 @@ export default function DmScreen() {
<SafeAreaView style={styles.container} edges={['top']}> <SafeAreaView style={styles.container} edges={['top']}>
{/* Header */} {/* Header */}
<View style={styles.header}> <View style={styles.header}>
<Pressable style={styles.backBtn} onPress={() => router.back()} hitSlop={8}> <TouchableOpacity style={styles.backBtn} onPress={() => router.back()} hitSlop={8} activeOpacity={0.7}>
<Ionicons name="chevron-back" size={22} color={colors.text} /> <Ionicons name="chevron-back" size={22} color={colors.text} />
</Pressable> </TouchableOpacity>
<View style={styles.headerCenter}> <View style={styles.headerCenter}>
<View style={styles.headerAvatar}> <View style={styles.headerAvatar}>
{partner?.avatar ? ( {partner?.avatar ? (

View File

@ -1,5 +1,5 @@
import { useEffect, useState } from 'react'; import { useEffect, useState } from 'react';
import { View, Text, Pressable, ScrollView } from 'react-native'; import { View, Text, TouchableOpacity, ScrollView } from 'react-native';
import { SafeAreaView } from 'react-native-safe-area-context'; import { SafeAreaView } from 'react-native-safe-area-context';
import { useRouter } from 'expo-router'; import { useRouter } from 'expo-router';
import { Ionicons } from '@expo/vector-icons'; import { Ionicons } from '@expo/vector-icons';
@ -83,26 +83,17 @@ export default function GamesScreen() {
borderBottomColor: colors.border, borderBottomColor: colors.border,
}} }}
> >
<Pressable <TouchableOpacity
onPress={() => exit()} onPress={() => exit()}
hitSlop={10} hitSlop={10}
style={({ pressed }) => ({ activeOpacity={0.6}
opacity: pressed ? 0.6 : 1, style={{ flexDirection: 'row', alignItems: 'center', gap: 4, paddingHorizontal: 6, paddingVertical: 6 }}
})}
> >
<View style={{ <Ionicons name="chevron-back" size={22} color={colors.text} />
flexDirection: 'row', <Text style={{ fontSize: 15, fontFamily: 'Nunito_600SemiBold', color: colors.text }}>
alignItems: 'center', {t('games.back_to_picker')}
gap: 4, </Text>
paddingHorizontal: 6, </TouchableOpacity>
paddingVertical: 6,
}}>
<Ionicons name="chevron-back" size={22} color={colors.text} />
<Text style={{ fontSize: 15, fontFamily: 'Nunito_600SemiBold', color: colors.text }}>
{t('games.back_to_picker')}
</Text>
</View>
</Pressable>
{/* Title bewusst entfernt der Game-Picker hat das Spiel schon ausgewählt, {/* Title bewusst entfernt der Game-Picker hat das Spiel schon ausgewählt,
Wiederholung im Header lenkt nur ab. Spacer balanciert den Back-Button. */} Wiederholung im Header lenkt nur ab. Spacer balanciert den Back-Button. */}
<View style={{ flex: 1 }} /> <View style={{ flex: 1 }} />
@ -141,22 +132,14 @@ export default function GamesScreen() {
borderBottomColor: colors.border, borderBottomColor: colors.border,
}} }}
> >
<Pressable <TouchableOpacity
onPress={() => router.back()} onPress={() => router.back()}
hitSlop={8} hitSlop={8}
style={({ pressed }) => ({ activeOpacity={0.6}
opacity: pressed ? 0.6 : 1, style={{ width: 40, height: 40, alignItems: 'center', justifyContent: 'center' }}
})}
> >
<View style={{ <Ionicons name="chevron-back" size={26} color={colors.text} />
width: 40, </TouchableOpacity>
height: 40,
alignItems: 'center',
justifyContent: 'center',
}}>
<Ionicons name="chevron-back" size={26} color={colors.text} />
</View>
</Pressable>
<Text style={{ fontSize: 20, color: colors.text, fontFamily: 'Nunito_700Bold' }}> <Text style={{ fontSize: 20, color: colors.text, fontFamily: 'Nunito_700Bold' }}>
{t('games.title')} {t('games.title')}
</Text> </Text>

View File

@ -1,4 +1,4 @@
import { View, Text, Pressable } from 'react-native'; import { View, Text, TouchableOpacity } from 'react-native';
import { useRouter } from 'expo-router'; import { useRouter } from 'expo-router';
import { SafeAreaView } from 'react-native-safe-area-context'; import { SafeAreaView } from 'react-native-safe-area-context';
import { useTranslation } from 'react-i18next'; import { useTranslation } from 'react-i18next';
@ -15,12 +15,13 @@ export default function HomeScreen() {
{t('landing.tagline')} {t('landing.tagline')}
</Text> </Text>
<Pressable <TouchableOpacity
onPress={() => router.push('/signin')} onPress={() => router.push('/signin')}
className="bg-rebreak-500 px-8 py-4 rounded-full active:opacity-80" activeOpacity={0.8}
className="bg-rebreak-500 px-8 py-4 rounded-full"
> >
<Text className="text-white text-base" style={{ fontFamily: 'Nunito_600SemiBold' }}>{t('landing.start')}</Text> <Text className="text-white text-base" style={{ fontFamily: 'Nunito_600SemiBold' }}>{t('landing.start')}</Text>
</Pressable> </TouchableOpacity>
<Text className="text-xs text-neutral-400 mt-8" style={{ fontFamily: 'Nunito_400Regular' }}> <Text className="text-xs text-neutral-400 mt-8" style={{ fontFamily: 'Nunito_400Regular' }}>
{t('landing.version')} {t('landing.version')}

View File

@ -9,7 +9,7 @@ import {
Text, Text,
TextInput, TextInput,
FlatList, FlatList,
Pressable, TouchableOpacity,
Platform, Platform,
Animated, Animated,
Keyboard, Keyboard,
@ -550,9 +550,9 @@ export default function CoachScreen() {
{/* Floating header — no bar, avatar + 2 icon buttons hover over chat */} {/* Floating header — no bar, avatar + 2 icon buttons hover over chat */}
<View style={[styles.topBar, { top: insets.top + 6 }]}> <View style={[styles.topBar, { top: insets.top + 6 }]}>
<Pressable style={[styles.backBtn, { backgroundColor: colorScheme === 'dark' ? 'rgba(44,44,46,0.92)' : 'rgba(255,255,255,0.92)' }]} onPress={() => router.replace('/(app)' as never)} hitSlop={12}> <TouchableOpacity style={[styles.backBtn, { backgroundColor: colorScheme === 'dark' ? 'rgba(44,44,46,0.92)' : 'rgba(255,255,255,0.92)' }]} onPress={() => router.replace('/(app)' as never)} hitSlop={12} activeOpacity={0.7}>
<Ionicons name="chevron-back" size={24} color={colors.text} /> <Ionicons name="chevron-back" size={24} color={colors.text} />
</Pressable> </TouchableOpacity>
<View style={styles.avatarCenter}> <View style={styles.avatarCenter}>
<View pointerEvents="none"> <View pointerEvents="none">
@ -587,17 +587,17 @@ export default function CoachScreen() {
<View style={styles.speakingRow}> <View style={styles.speakingRow}>
<VoiceBars count={5} baseColor={colors.brandOrange} /> <VoiceBars count={5} baseColor={colors.brandOrange} />
<Text style={[styles.speakingLabel, { color: colors.brandOrange }]}>{t('coach.speaking')}</Text> <Text style={[styles.speakingLabel, { color: colors.brandOrange }]}>{t('coach.speaking')}</Text>
<Pressable style={[styles.stopBtn, { backgroundColor: colors.surfaceElevated }]} onPress={stopSpeaking} hitSlop={6}> <TouchableOpacity style={[styles.stopBtn, { backgroundColor: colors.surfaceElevated }]} onPress={stopSpeaking} hitSlop={6} activeOpacity={0.7}>
<Ionicons name="square" size={10} color={colors.brandOrange} /> <Ionicons name="square" size={10} color={colors.brandOrange} />
</Pressable> </TouchableOpacity>
</View> </View>
)} )}
</View> </View>
</View> </View>
<Pressable style={[styles.newChatBtn, { backgroundColor: colorScheme === 'dark' ? 'rgba(44,44,46,0.92)' : 'rgba(255,255,255,0.92)' }]} onPress={handleNewChat} hitSlop={12}> <TouchableOpacity style={[styles.newChatBtn, { backgroundColor: colorScheme === 'dark' ? 'rgba(44,44,46,0.92)' : 'rgba(255,255,255,0.92)' }]} onPress={handleNewChat} hitSlop={12} activeOpacity={0.7}>
<Ionicons name="refresh-outline" size={22} color={colors.textMuted} /> <Ionicons name="refresh-outline" size={22} color={colors.textMuted} />
</Pressable> </TouchableOpacity>
</View> </View>
{/* Content area */} {/* Content area */}
@ -637,9 +637,9 @@ export default function CoachScreen() {
{/* Scroll-to-bottom button */} {/* Scroll-to-bottom button */}
{showScrollBtn && ( {showScrollBtn && (
<Pressable style={styles.scrollDownBtn} onPress={scrollToBottom}> <TouchableOpacity style={styles.scrollDownBtn} onPress={scrollToBottom} activeOpacity={0.8}>
<Ionicons name="chevron-down" size={18} color="#fff" /> <Ionicons name="chevron-down" size={18} color="#fff" />
</Pressable> </TouchableOpacity>
)} )}
</View> </View>
)} )}
@ -648,9 +648,9 @@ export default function CoachScreen() {
<View style={[styles.inputBar, { paddingBottom: keyboardHeight > 0 ? 8 : Math.max(12, insets.bottom), backgroundColor: colors.bg, borderTopColor: colors.border }]}> <View style={[styles.inputBar, { paddingBottom: keyboardHeight > 0 ? 8 : Math.max(12, insets.bottom), backgroundColor: colors.bg, borderTopColor: colors.border }]}>
{isRecording ? ( {isRecording ? (
<View style={styles.recordingContainer}> <View style={styles.recordingContainer}>
<Pressable style={styles.cancelBtn} onPress={cancelRecording}> <TouchableOpacity style={styles.cancelBtn} onPress={cancelRecording} activeOpacity={0.7}>
<Ionicons name="trash" size={16} color="#f87171" /> <Ionicons name="trash" size={16} color="#f87171" />
</Pressable> </TouchableOpacity>
<View style={styles.pulseDot} /> <View style={styles.pulseDot} />
<Text style={styles.recordingTimer}>{formatDuration(recordingDuration)}</Text> <Text style={styles.recordingTimer}>{formatDuration(recordingDuration)}</Text>
<View style={{ flex: 1 }}> <View style={{ flex: 1 }}>
@ -677,28 +677,30 @@ export default function CoachScreen() {
)} )}
{!isTranscribing && ( {!isTranscribing && (
<Pressable <TouchableOpacity
style={[styles.micBtn, { backgroundColor: colors.surfaceElevated }, isRecording && styles.micBtnActive, thinking && styles.micBtnDisabled]} style={[styles.micBtn, { backgroundColor: colors.surfaceElevated }, isRecording && styles.micBtnActive, thinking && styles.micBtnDisabled]}
onPressIn={onMicDown} onPressIn={onMicDown}
onPressOut={onMicUp} onPressOut={onMicUp}
disabled={thinking} disabled={thinking}
activeOpacity={0.7}
> >
<Ionicons <Ionicons
name={isRecording ? 'square' : 'mic'} name={isRecording ? 'square' : 'mic'}
size={18} size={18}
color={isRecording ? '#fff' : colors.textMuted} color={isRecording ? '#fff' : colors.textMuted}
/> />
</Pressable> </TouchableOpacity>
)} )}
{!isRecording && !isTranscribing && input.trim() !== '' && ( {!isRecording && !isTranscribing && input.trim() !== '' && (
<Pressable <TouchableOpacity
style={[styles.sendBtn, thinking && styles.sendBtnDisabled]} style={[styles.sendBtn, thinking && styles.sendBtnDisabled]}
onPress={handleSend} onPress={handleSend}
disabled={thinking || !input.trim()} disabled={thinking || !input.trim()}
activeOpacity={0.7}
> >
<Ionicons name="send" size={16} color="#fff" /> <Ionicons name="send" size={16} color="#fff" />
</Pressable> </TouchableOpacity>
)} )}
</View> </View>
</KeyboardAvoidingView> </KeyboardAvoidingView>

View File

@ -1,5 +1,5 @@
import { useState } from 'react'; import { useState } from 'react';
import { View, Text, ScrollView, Pressable, Image } from 'react-native'; import { View, Text, ScrollView, TouchableOpacity, Image } from 'react-native';
import { useSafeAreaInsets } from 'react-native-safe-area-context'; import { useSafeAreaInsets } from 'react-native-safe-area-context';
import { useLocalSearchParams, useRouter } from 'expo-router'; import { useLocalSearchParams, useRouter } from 'expo-router';
import { Ionicons } from '@expo/vector-icons'; import { Ionicons } from '@expo/vector-icons';
@ -119,13 +119,14 @@ export default function ForeignProfileScreen() {
paddingHorizontal: 12, paddingHorizontal: 12,
}} }}
> >
<Pressable <TouchableOpacity
onPress={() => router.back()} onPress={() => router.back()}
hitSlop={8} hitSlop={8}
style={({ pressed }) => ({ opacity: pressed ? 0.5 : 1, padding: 8 })} activeOpacity={0.5}
style={{ padding: 8 }}
> >
<Ionicons name="chevron-back" size={22} color={colors.text} /> <Ionicons name="chevron-back" size={22} color={colors.text} />
</Pressable> </TouchableOpacity>
<Text style={{ fontSize: 15, color: colors.text, fontFamily: 'Nunito_600SemiBold' }}> <Text style={{ fontSize: 15, color: colors.text, fontFamily: 'Nunito_600SemiBold' }}>
Profil Profil
</Text> </Text>
@ -204,15 +205,13 @@ export default function ForeignProfileScreen() {
</View> </View>
<View style={{ flexDirection: 'row', gap: 8, marginTop: 16, width: '100%' }}> <View style={{ flexDirection: 'row', gap: 8, marginTop: 16, width: '100%' }}>
<Pressable <TouchableOpacity
onPress={() => { onPress={() => {
// TODO: POST /api/social/follow/[userId] resp. DELETE bei unfollow // TODO: POST /api/social/follow/[userId] resp. DELETE bei unfollow
setIsFollowing((v) => !v); setIsFollowing((v) => !v);
}} }}
style={({ pressed }) => ({ activeOpacity={0.7}
flex: 1, style={{ flex: 1 }}
opacity: pressed ? 0.7 : 1,
})}
> >
<View style={{ <View style={{
paddingVertical: 11, paddingVertical: 11,
@ -232,16 +231,14 @@ export default function ForeignProfileScreen() {
{isFollowing ? 'Folge ich' : 'Folgen'} {isFollowing ? 'Folge ich' : 'Folgen'}
</Text> </Text>
</View> </View>
</Pressable> </TouchableOpacity>
<Pressable <TouchableOpacity
onPress={() => { onPress={() => {
// TODO: navigate to DM with this userId // TODO: navigate to DM with this userId
router.push(`/dm`); router.push(`/dm`);
}} }}
style={({ pressed }) => ({ activeOpacity={0.7}
flex: 1, style={{ flex: 1 }}
opacity: pressed ? 0.7 : 1,
})}
> >
<View style={{ <View style={{
paddingVertical: 11, paddingVertical: 11,
@ -261,7 +258,7 @@ export default function ForeignProfileScreen() {
Nachricht Nachricht
</Text> </Text>
</View> </View>
</Pressable> </TouchableOpacity>
</View> </View>
</View> </View>

View File

@ -3,7 +3,7 @@ import {
View, View,
Text, Text,
TextInput, TextInput,
Pressable, TouchableOpacity,
ScrollView, ScrollView,
Image, Image,
ActivityIndicator, ActivityIndicator,
@ -141,22 +141,22 @@ export default function ProfileEditScreen() {
backgroundColor: colors.bg, backgroundColor: colors.bg,
}} }}
> >
<Pressable <TouchableOpacity
onPress={() => router.back()} onPress={() => router.back()}
hitSlop={10} hitSlop={10}
style={({ pressed }) => ({ opacity: pressed ? 0.5 : 1, marginRight: 12 })} activeOpacity={0.5}
style={{ marginRight: 12 }}
> >
<Ionicons name="chevron-back" size={24} color={colors.text} /> <Ionicons name="chevron-back" size={24} color={colors.text} />
</Pressable> </TouchableOpacity>
<Text style={{ flex: 1, fontSize: 17, color: colors.text, fontFamily: 'Nunito_700Bold' }}> <Text style={{ flex: 1, fontSize: 17, color: colors.text, fontFamily: 'Nunito_700Bold' }}>
{t('profile.edit_title')} {t('profile.edit_title')}
</Text> </Text>
<Pressable <TouchableOpacity
onPress={save} onPress={save}
disabled={saving || !hasChanges || !nickname.trim()} disabled={saving || !hasChanges || !nickname.trim()}
style={({ pressed }) => ({ activeOpacity={0.4}
opacity: pressed || saving || !hasChanges || !nickname.trim() ? 0.4 : 1, style={{ opacity: saving || !hasChanges || !nickname.trim() ? 0.4 : 1 }}
})}
> >
{saving ? ( {saving ? (
<ActivityIndicator size="small" color={colors.brandOrange} /> <ActivityIndicator size="small" color={colors.brandOrange} />
@ -171,7 +171,7 @@ export default function ProfileEditScreen() {
{t('profile.edit_save')} {t('profile.edit_save')}
</Text> </Text>
)} )}
</Pressable> </TouchableOpacity>
</View> </View>
<ScrollView <ScrollView
@ -209,15 +209,10 @@ export default function ProfileEditScreen() {
) : null} ) : null}
</View> </View>
<Pressable <TouchableOpacity
onPress={pickPhoto} onPress={pickPhoto}
style={({ pressed }) => ({ activeOpacity={0.5}
marginTop: 12, style={{ marginTop: 12, flexDirection: 'row', alignItems: 'center', gap: 6 }}
flexDirection: 'row',
alignItems: 'center',
gap: 6,
opacity: pressed ? 0.5 : 1,
})}
> >
<Ionicons name="camera-outline" size={18} color={colors.brandOrange} /> <Ionicons name="camera-outline" size={18} color={colors.brandOrange} />
<Text <Text
@ -229,7 +224,7 @@ export default function ProfileEditScreen() {
> >
{t('profile.edit_photo_cta')} {t('profile.edit_photo_cta')}
</Text> </Text>
</Pressable> </TouchableOpacity>
</View> </View>
{/* Preset avatars */} {/* Preset avatars */}
@ -249,15 +244,13 @@ export default function ProfileEditScreen() {
{HERO_AVATARS.map((avatar) => { {HERO_AVATARS.map((avatar) => {
const isSelected = !photoUri && avatarId === avatar.id; const isSelected = !photoUri && avatarId === avatar.id;
return ( return (
<Pressable <TouchableOpacity
key={avatar.id} key={avatar.id}
onPress={() => { onPress={() => {
setAvatarId(avatar.id); setAvatarId(avatar.id);
setPhotoUri(null); setPhotoUri(null);
}} }}
style={({ pressed }) => ({ activeOpacity={0.7}
opacity: pressed ? 0.7 : 1,
})}
> >
<Image <Image
source={{ uri: getAvatarUrl(avatar.id) }} source={{ uri: getAvatarUrl(avatar.id) }}
@ -270,7 +263,7 @@ export default function ProfileEditScreen() {
opacity: isSelected ? 1 : 0.55, opacity: isSelected ? 1 : 0.55,
}} }}
/> />
</Pressable> </TouchableOpacity>
); );
})} })}
</View> </View>

View File

@ -4,6 +4,7 @@ import {
Text, Text,
FlatList, FlatList,
Pressable, Pressable,
TouchableOpacity,
Image, Image,
Modal, Modal,
TextInput, TextInput,
@ -299,9 +300,9 @@ export default function RoomScreen() {
<SafeAreaView style={styles.container} edges={['top']}> <SafeAreaView style={styles.container} edges={['top']}>
{/* Header */} {/* Header */}
<View style={styles.header}> <View style={styles.header}>
<Pressable style={styles.iconBtn} onPress={() => router.back()} hitSlop={8}> <TouchableOpacity style={styles.iconBtn} onPress={() => router.back()} hitSlop={8} activeOpacity={0.7}>
<Ionicons name="chevron-back" size={22} color={colors.text} /> <Ionicons name="chevron-back" size={22} color={colors.text} />
</Pressable> </TouchableOpacity>
<View style={styles.headerCenter}> <View style={styles.headerCenter}>
<View style={styles.headerAvatar}> <View style={styles.headerAvatar}>
{room?.avatarUrl ? ( {room?.avatarUrl ? (
@ -321,9 +322,9 @@ export default function RoomScreen() {
)} )}
</View> </View>
</View> </View>
<Pressable style={styles.iconBtn} onPress={() => setSettingsOpen(true)} hitSlop={8}> <TouchableOpacity style={styles.iconBtn} onPress={() => setSettingsOpen(true)} hitSlop={8} activeOpacity={0.7}>
<Ionicons name="ellipsis-horizontal" size={20} color={colors.text} /> <Ionicons name="ellipsis-horizontal" size={20} color={colors.text} />
</Pressable> </TouchableOpacity>
</View> </View>
{isLoading || !room ? ( {isLoading || !room ? (
@ -342,20 +343,18 @@ export default function RoomScreen() {
<Text style={styles.pendingText}>{t('chat.join_pending')}</Text> <Text style={styles.pendingText}>{t('chat.join_pending')}</Text>
</View> </View>
) : ( ) : (
<Pressable <TouchableOpacity
onPress={handleJoin} onPress={handleJoin}
disabled={joining} disabled={joining}
style={({ pressed }) => [ activeOpacity={0.7}
styles.joinBtn, style={[styles.joinBtn, joining && { opacity: 0.7 }]}
{ opacity: pressed || joining ? 0.7 : 1 },
]}
> >
{joining ? ( {joining ? (
<ActivityIndicator color="#fff" /> <ActivityIndicator color="#fff" />
) : ( ) : (
<Text style={styles.joinBtnText}>{t('chat.join')}</Text> <Text style={styles.joinBtnText}>{t('chat.join')}</Text>
)} )}
</Pressable> </TouchableOpacity>
)} )}
</View> </View>
) : ( ) : (
@ -519,9 +518,9 @@ function RoomSettingsModal({
<Modal visible={visible} animationType="slide" presentationStyle="pageSheet" onRequestClose={onClose}> <Modal visible={visible} animationType="slide" presentationStyle="pageSheet" onRequestClose={onClose}>
<SafeAreaView style={modal.container} edges={['top']}> <SafeAreaView style={modal.container} edges={['top']}>
<View style={modal.header}> <View style={modal.header}>
<Pressable onPress={onClose} hitSlop={8}> <TouchableOpacity onPress={onClose} hitSlop={8} activeOpacity={0.7}>
<Ionicons name="close" size={24} color="#0a0a0a" /> <Ionicons name="close" size={24} color="#0a0a0a" />
</Pressable> </TouchableOpacity>
<Text style={modal.title}>{t('chat.settings')}</Text> <Text style={modal.title}>{t('chat.settings')}</Text>
<View style={{ width: 24 }} /> <View style={{ width: 24 }} />
</View> </View>
@ -529,8 +528,9 @@ function RoomSettingsModal({
<ScrollView contentContainerStyle={{ padding: 16, paddingBottom: 60 }}> <ScrollView contentContainerStyle={{ padding: 16, paddingBottom: 60 }}>
{/* Avatar + Name */} {/* Avatar + Name */}
<View style={modal.section}> <View style={modal.section}>
<Pressable <TouchableOpacity
onPress={isAdmin ? onAvatarChange : undefined} onPress={isAdmin ? onAvatarChange : undefined}
activeOpacity={0.7}
style={modal.avatarWrap} style={modal.avatarWrap}
> >
{room.avatarUrl ? ( {room.avatarUrl ? (
@ -545,7 +545,7 @@ function RoomSettingsModal({
<Ionicons name="camera" size={14} color="#fff" /> <Ionicons name="camera" size={14} color="#fff" />
</View> </View>
)} )}
</Pressable> </TouchableOpacity>
<Text style={modal.roomName}>{room.name}</Text> <Text style={modal.roomName}>{room.name}</Text>
{room.description && <Text style={modal.roomDesc}>{room.description}</Text>} {room.description && <Text style={modal.roomDesc}>{room.description}</Text>}
</View> </View>
@ -564,22 +564,24 @@ function RoomSettingsModal({
<View style={{ flex: 1 }}> <View style={{ flex: 1 }}>
<Text style={modal.memberName}>{req.nickname ?? 'Anonym'}</Text> <Text style={modal.memberName}>{req.nickname ?? 'Anonym'}</Text>
</View> </View>
<Pressable <TouchableOpacity
style={[modal.actionBtn, { backgroundColor: '#dcfce7' }]} style={[modal.actionBtn, { backgroundColor: '#dcfce7' }]}
onPress={() => handleRequest(req.userId, 'approve')} onPress={() => handleRequest(req.userId, 'approve')}
activeOpacity={0.7}
> >
<Text style={[modal.actionText, { color: '#166534' }]}> <Text style={[modal.actionText, { color: '#166534' }]}>
{t('chat.approve')} {t('chat.approve')}
</Text> </Text>
</Pressable> </TouchableOpacity>
<Pressable <TouchableOpacity
style={[modal.actionBtn, { backgroundColor: '#fee2e2', marginLeft: 6 }]} style={[modal.actionBtn, { backgroundColor: '#fee2e2', marginLeft: 6 }]}
onPress={() => handleRequest(req.userId, 'reject')} onPress={() => handleRequest(req.userId, 'reject')}
activeOpacity={0.7}
> >
<Text style={[modal.actionText, { color: '#991b1b' }]}> <Text style={[modal.actionText, { color: '#991b1b' }]}>
{t('chat.reject')} {t('chat.reject')}
</Text> </Text>
</Pressable> </TouchableOpacity>
</View> </View>
)) ))
)} )}
@ -610,18 +612,20 @@ function RoomSettingsModal({
</View> </View>
{isAdmin && m.role === 'member' && ( {isAdmin && m.role === 'member' && (
<> <>
<Pressable <TouchableOpacity
style={[modal.actionBtn, { backgroundColor: '#fef3c7' }]} style={[modal.actionBtn, { backgroundColor: '#fef3c7' }]}
onPress={() => handlePromote(m.userId)} onPress={() => handlePromote(m.userId)}
activeOpacity={0.7}
> >
<Text style={[modal.actionText, { color: '#92400e' }]}>Admin</Text> <Text style={[modal.actionText, { color: '#92400e' }]}>Admin</Text>
</Pressable> </TouchableOpacity>
<Pressable <TouchableOpacity
style={[modal.actionBtn, { backgroundColor: '#fee2e2', marginLeft: 6 }]} style={[modal.actionBtn, { backgroundColor: '#fee2e2', marginLeft: 6 }]}
onPress={() => handleBan(m.userId)} onPress={() => handleBan(m.userId)}
activeOpacity={0.7}
> >
<Text style={[modal.actionText, { color: '#991b1b' }]}>Ban</Text> <Text style={[modal.actionText, { color: '#991b1b' }]}>Ban</Text>
</Pressable> </TouchableOpacity>
</> </>
)} )}
</View> </View>
@ -630,10 +634,10 @@ function RoomSettingsModal({
{/* Leave */} {/* Leave */}
{!room.isDefault && ( {!room.isDefault && (
<Pressable style={modal.leaveBtn} onPress={handleLeave}> <TouchableOpacity style={modal.leaveBtn} onPress={handleLeave} activeOpacity={0.7}>
<Ionicons name="exit-outline" size={18} color="#991b1b" /> <Ionicons name="exit-outline" size={18} color="#991b1b" />
<Text style={modal.leaveText}>{t('chat.leave_room')}</Text> <Text style={modal.leaveText}>{t('chat.leave_room')}</Text>
</Pressable> </TouchableOpacity>
)} )}
</ScrollView> </ScrollView>
</SafeAreaView> </SafeAreaView>

View File

@ -2,9 +2,9 @@ import {
Alert, Alert,
Linking, Linking,
Platform, Platform,
Pressable,
ScrollView, ScrollView,
Text, Text,
TouchableOpacity,
View, View,
} from 'react-native'; } from 'react-native';
import { useRef, useState } from 'react'; import { useRef, useState } from 'react';
@ -100,7 +100,7 @@ function SubscriptionSheet({ plan, colors, t }: SubscriptionSheetProps) {
{t('settings.subscription_sheet_body')} {t('settings.subscription_sheet_body')}
</Text> </Text>
<Pressable <TouchableOpacity
onPress={() => { onPress={() => {
// TODO: für iOS-Submission ggf. zu nicht-tippbarem Text degradieren // TODO: für iOS-Submission ggf. zu nicht-tippbarem Text degradieren
// (Apple Guideline 3.1.1: externe Abo-Links können Review-Ablehnung triggern, // (Apple Guideline 3.1.1: externe Abo-Links können Review-Ablehnung triggern,
@ -108,13 +108,13 @@ function SubscriptionSheet({ plan, colors, t }: SubscriptionSheetProps) {
// sollte ok sein, ist aber ungeprüft — bei Submission erneut prüfen.) // sollte ok sein, ist aber ungeprüft — bei Submission erneut prüfen.)
Linking.openURL('https://rebreak.org/account'); Linking.openURL('https://rebreak.org/account');
}} }}
style={({ pressed }) => ({ activeOpacity={0.8}
style={{
backgroundColor: accentColor, backgroundColor: accentColor,
borderRadius: 14, borderRadius: 14,
paddingVertical: 14, paddingVertical: 14,
alignItems: 'center', alignItems: 'center',
opacity: pressed ? 0.8 : 1, }}
})}
> >
<Text <Text
style={{ style={{
@ -125,7 +125,7 @@ function SubscriptionSheet({ plan, colors, t }: SubscriptionSheetProps) {
> >
{t('settings.subscription_sheet_cta')} {t('settings.subscription_sheet_cta')}
</Text> </Text>
</Pressable> </TouchableOpacity>
</View> </View>
); );
} }
@ -472,9 +472,9 @@ export default function SettingsScreen() {
} }
shouldOpenOnLongPress={false} shouldOpenOnLongPress={false}
> >
<Pressable <TouchableOpacity
hitSlop={8} hitSlop={8}
style={({ pressed }) => ({ opacity: pressed ? 0.6 : 1 })} activeOpacity={0.6}
> >
<View <View
style={{ style={{
@ -505,21 +505,19 @@ export default function SettingsScreen() {
color={colors.textMuted} color={colors.textMuted}
/> />
</View> </View>
</Pressable> </TouchableOpacity>
</MenuView> </MenuView>
</View> </View>
); );
} }
// Standard-Row: ganze Pressable als Tap-Target
return ( return (
<Pressable <TouchableOpacity
key={row.label} key={row.label}
onPress={row.soon ? undefined : row.onPress} onPress={row.soon ? undefined : row.onPress}
disabled={row.soon} disabled={row.soon}
style={({ pressed }) => ({ activeOpacity={0.7}
opacity: row.soon ? 0.5 : pressed ? 0.7 : 1, style={{ opacity: row.soon ? 0.5 : 1 }}
})}
> >
<View style={containerStyle}> <View style={containerStyle}>
{rowLeft} {rowLeft}
@ -555,7 +553,7 @@ export default function SettingsScreen() {
/> />
)} )}
</View> </View>
</Pressable> </TouchableOpacity>
); );
})} })}
</View> </View>
@ -630,13 +628,13 @@ export default function SettingsScreen() {
{voiceOptions.map((opt, idx) => { {voiceOptions.map((opt, idx) => {
const isSelected = opt.value === selectedVoice; const isSelected = opt.value === selectedVoice;
return ( return (
<Pressable <TouchableOpacity
key={opt.value} key={opt.value}
onPress={() => { onPress={() => {
setSelectedVoice(opt.value); setSelectedVoice(opt.value);
voiceSheetRef.current?.dismiss(); voiceSheetRef.current?.dismiss();
}} }}
style={({ pressed }) => ({ opacity: pressed ? 0.6 : 1 })} activeOpacity={0.6}
> >
<View <View
style={{ style={{
@ -661,7 +659,7 @@ export default function SettingsScreen() {
<Ionicons name="checkmark" size={20} color={colors.brandOrange} /> <Ionicons name="checkmark" size={20} color={colors.brandOrange} />
) : null} ) : null}
</View> </View>
</Pressable> </TouchableOpacity>
); );
})} })}
</View> </View>

View File

@ -1227,11 +1227,7 @@ export default function SOSScreen() {
key={chip.action} key={chip.action}
onPress={() => handleChip(chip.action)} onPress={() => handleChip(chip.action)}
disabled={thinking} disabled={thinking}
style={({ pressed }) => [ style={[st.chip, thinking && { opacity: 0.4 }]}
st.chip,
pressed && st.chipPressed,
thinking && { opacity: 0.4 },
]}
> >
<View style={{ flexDirection: 'row', alignItems: 'center', gap: 6 }}> <View style={{ flexDirection: 'row', alignItems: 'center', gap: 6 }}>
{iconName && <Ionicons name={iconName} size={15} color={iconColor} />} {iconName && <Ionicons name={iconName} size={15} color={iconColor} />}

View File

@ -4,6 +4,7 @@ import {
Text, Text,
TextInput, TextInput,
Pressable, Pressable,
TouchableOpacity,
Image, Image,
ActivityIndicator, ActivityIndicator,
Alert, Alert,
@ -133,7 +134,6 @@ export function ComposeCard({ onPosted }: Props) {
hitSlop={{ top: 9, bottom: 9, left: 9, right: 9 }} hitSlop={{ top: 9, bottom: 9, left: 9, right: 9 }}
android_ripple={{ color: 'rgba(255,255,255,0.18)', borderless: true, radius: 22 }} android_ripple={{ color: 'rgba(255,255,255,0.18)', borderless: true, radius: 22 }}
className="absolute top-2 right-2 w-7 h-7 rounded-full bg-black/50 items-center justify-center" className="absolute top-2 right-2 w-7 h-7 rounded-full bg-black/50 items-center justify-center"
style={({ pressed }) => ({ opacity: pressed ? 0.7 : 1 })}
> >
<Ionicons name="close" size={14} color="#fff" /> <Ionicons name="close" size={14} color="#fff" />
</Pressable> </Pressable>
@ -148,34 +148,33 @@ export function ComposeCard({ onPosted }: Props) {
onPress={pickImage} onPress={pickImage}
android_ripple={{ color: 'rgba(0,0,0,0.08)', borderless: true, radius: 22 }} android_ripple={{ color: 'rgba(0,0,0,0.08)', borderless: true, radius: 22 }}
className="flex-row items-center gap-1.5 px-2" className="flex-row items-center gap-1.5 px-2"
style={({ pressed }) => ({ opacity: pressed ? 0.6 : 1, height: 44 })} style={{ height: 44 }}
> >
<Ionicons name="image-outline" size={22} color="#737373" /> <Ionicons name="image-outline" size={22} color="#737373" />
<Text className="text-sm text-neutral-500" style={{ fontFamily: 'Nunito_400Regular' }}>{t('community.image')}</Text> <Text className="text-sm text-neutral-500" style={{ fontFamily: 'Nunito_400Regular' }}>{t('community.image')}</Text>
</Pressable> </Pressable>
<View className="flex-row items-center gap-2"> <View className="flex-row items-center gap-2">
<Pressable <TouchableOpacity
onPress={cancel} onPress={cancel}
hitSlop={{ top: 12, bottom: 12, left: 12, right: 12 }} hitSlop={{ top: 12, bottom: 12, left: 12, right: 12 }}
style={({ pressed }) => ({ opacity: pressed ? 0.5 : 1 })} activeOpacity={0.5}
> >
<Text className="text-sm text-neutral-400" style={{ fontFamily: 'Nunito_600SemiBold' }}>{t('common.cancel')}</Text> <Text className="text-sm text-neutral-400" style={{ fontFamily: 'Nunito_600SemiBold' }}>{t('common.cancel')}</Text>
</Pressable> </TouchableOpacity>
<Pressable <TouchableOpacity
onPressIn={() => { if (!content.trim() || posting) return; submit(); }} onPress={() => { if (!content.trim() || posting) return; submit(); }}
disabled={!content.trim() || posting} disabled={!content.trim() || posting}
activeOpacity={0.5}
className="bg-rebreak-500 items-center justify-center rounded-full px-5 h-8" className="bg-rebreak-500 items-center justify-center rounded-full px-5 h-8"
style={({ pressed }) => ({ style={{ opacity: !content.trim() || posting ? 0.5 : 1 }}
opacity: pressed || !content.trim() || posting ? 0.5 : 1,
})}
> >
{posting ? ( {posting ? (
<ActivityIndicator size="small" color="#fff" /> <ActivityIndicator size="small" color="#fff" />
) : ( ) : (
<Text className="text-white text-sm" style={{ fontFamily: 'Nunito_600SemiBold' }}>{t('community.share')}</Text> <Text className="text-white text-sm" style={{ fontFamily: 'Nunito_600SemiBold' }}>{t('community.share')}</Text>
)} )}
</Pressable> </TouchableOpacity>
</View> </View>
</View> </View>
)} )}

View File

@ -1,5 +1,5 @@
import { useEffect, useRef } from 'react'; import { useEffect, useRef } from 'react';
import { Modal, View, Text, Pressable, Animated, Easing } from 'react-native'; import { Modal, View, Text, TouchableOpacity, Animated, Easing } from 'react-native';
// Wichtig (UX-Entscheidung 2026-05-05): Icon im Confirm-Modal NICHT animieren — // Wichtig (UX-Entscheidung 2026-05-05): Icon im Confirm-Modal NICHT animieren —
// User sieht zwei Modals nacheinander (Confirm → Success), beide animierte Icons // User sieht zwei Modals nacheinander (Confirm → Success), beide animierte Icons
// = visuelle Doppel-Eskalation, wirkt verwirrend. Daher: Card animiert auf, // = visuelle Doppel-Eskalation, wirkt verwirrend. Daher: Card animiert auf,
@ -74,7 +74,8 @@ export function ConfirmAlert({
return ( return (
<Modal visible={visible} transparent animationType="fade" onRequestClose={onCancel}> <Modal visible={visible} transparent animationType="fade" onRequestClose={onCancel}>
<Pressable <TouchableOpacity
activeOpacity={1}
onPress={onCancel} onPress={onCancel}
style={{ style={{
flex: 1, flex: 1,
@ -84,7 +85,7 @@ export function ConfirmAlert({
padding: 24, padding: 24,
}} }}
> >
<Pressable onPress={() => {}} style={{ width: '85%', maxWidth: 340 }}> <TouchableOpacity activeOpacity={1} onPress={() => {}} style={{ width: '85%', maxWidth: 340 }}>
<Animated.View <Animated.View
style={{ style={{
backgroundColor: '#fff', backgroundColor: '#fff',
@ -150,7 +151,8 @@ export function ConfirmAlert({
{/* Two buttons row */} {/* Two buttons row */}
<View style={{ flexDirection: 'row' }}> <View style={{ flexDirection: 'row' }}>
<View style={{ flex: 1, marginRight: 5 }}> <View style={{ flex: 1, marginRight: 5 }}>
<Pressable <TouchableOpacity
activeOpacity={0.7}
onPress={onCancel} onPress={onCancel}
style={{ style={{
paddingVertical: 10, paddingVertical: 10,
@ -169,10 +171,11 @@ export function ConfirmAlert({
> >
{resolvedCancelLabel} {resolvedCancelLabel}
</Text> </Text>
</Pressable> </TouchableOpacity>
</View> </View>
<View style={{ flex: 1, marginLeft: 5 }}> <View style={{ flex: 1, marginLeft: 5 }}>
<Pressable <TouchableOpacity
activeOpacity={0.7}
onPress={onConfirm} onPress={onConfirm}
style={{ style={{
paddingVertical: 10, paddingVertical: 10,
@ -193,12 +196,12 @@ export function ConfirmAlert({
> >
{resolvedConfirmLabel} {resolvedConfirmLabel}
</Text> </Text>
</Pressable> </TouchableOpacity>
</View> </View>
</View> </View>
</Animated.View> </Animated.View>
</Pressable> </TouchableOpacity>
</Pressable> </TouchableOpacity>
</Modal> </Modal>
); );
} }

View File

@ -1,4 +1,4 @@
import { ActivityIndicator, Platform, Pressable, Text, View } from 'react-native'; import { ActivityIndicator, Platform, TouchableOpacity, Text, View } from 'react-native';
import { useEffect, useRef, useState } from 'react'; import { useEffect, useRef, useState } from 'react';
import { TrueSheet, type SheetDetent } from '@lodev09/react-native-true-sheet'; import { TrueSheet, type SheetDetent } from '@lodev09/react-native-true-sheet';
import { Ionicons } from '@expo/vector-icons'; import { Ionicons } from '@expo/vector-icons';
@ -106,13 +106,13 @@ function DeviceLimitRow({
{removing ? ( {removing ? (
<ActivityIndicator size="small" color={colors.error} /> <ActivityIndicator size="small" color={colors.error} />
) : ( ) : (
<Pressable <TouchableOpacity
onPress={() => onRemove(device.id)} onPress={() => onRemove(device.id)}
hitSlop={8} hitSlop={8}
style={({ pressed }) => ({ opacity: pressed ? 0.5 : 1 })} activeOpacity={0.5}
> >
<Ionicons name="trash-outline" size={18} color={colors.error} /> <Ionicons name="trash-outline" size={18} color={colors.error} />
</Pressable> </TouchableOpacity>
)} )}
</View> </View>
); );

View File

@ -5,7 +5,7 @@ import {
Keyboard, Keyboard,
Modal, Modal,
Platform, Platform,
Pressable, TouchableOpacity,
StyleProp, StyleProp,
View, View,
ViewStyle, ViewStyle,
@ -162,7 +162,7 @@ export function KeyboardAwareSheet({
opacity: backdropOpacity, opacity: backdropOpacity,
}} }}
> >
{dismissOnBackdrop && <Pressable style={{ flex: 1 }} onPress={onClose} />} {dismissOnBackdrop && <TouchableOpacity activeOpacity={1} style={{ flex: 1 }} onPress={onClose} />}
</Animated.View> </Animated.View>
{/* Outer: animated height (JS-driver) */} {/* Outer: animated height (JS-driver) */}

View File

@ -1,5 +1,5 @@
import { useEffect, useRef } from 'react'; import { useEffect, useRef } from 'react';
import { View, Text, Pressable, Modal, FlatList, Animated, Image } from 'react-native'; import { View, Text, Pressable, TouchableOpacity, Modal, FlatList, Animated, Image } from 'react-native';
import { Ionicons } from '@expo/vector-icons'; import { Ionicons } from '@expo/vector-icons';
import { useRouter, type RelativePathString } from 'expo-router'; import { useRouter, type RelativePathString } from 'expo-router';
import { useTranslation } from 'react-i18next'; import { useTranslation } from 'react-i18next';
@ -239,11 +239,9 @@ function NotificationRow({
const avatarUrl = isSocial ? resolveAvatar(notif.actorAvatar, notif.actorName) : null; const avatarUrl = isSocial ? resolveAvatar(notif.actorAvatar, notif.actorName) : null;
return ( return (
<Pressable <TouchableOpacity
onPress={onPress} onPress={onPress}
style={({ pressed }) => ({ activeOpacity={0.65}
opacity: pressed ? 0.65 : 1,
})}
> >
<View <View
style={{ style={{
@ -319,6 +317,6 @@ function NotificationRow({
</Text> </Text>
</View> </View>
</View> </View>
</Pressable> </TouchableOpacity>
); );
} }

View File

@ -18,6 +18,7 @@ import {
View, View,
Text, Text,
Pressable, Pressable,
TouchableOpacity,
Animated, Animated,
Easing, Easing,
} from 'react-native'; } from 'react-native';
@ -177,15 +178,13 @@ export function OptionsBottomSheet<T extends string | number>({
const isSelected = const isSelected =
value !== null && value !== undefined && opt.value === value; value !== null && value !== undefined && opt.value === value;
return ( return (
<Pressable <TouchableOpacity
key={String(opt.value)} key={String(opt.value)}
onPress={() => { onPress={() => {
onSelect(opt.value); onSelect(opt.value);
close(); close();
}} }}
style={({ pressed }) => ({ activeOpacity={0.7}
backgroundColor: pressed ? 'rgba(0,0,0,0.06)' : 'transparent',
})}
> >
<View <View
style={{ style={{
@ -207,38 +206,34 @@ export function OptionsBottomSheet<T extends string | number>({
{opt.label} {opt.label}
</Text> </Text>
</View> </View>
</Pressable> </TouchableOpacity>
); );
})} })}
</View> </View>
{/* Cancel-Card — separat, bold */} {/* Cancel-Card — separat, bold */}
<Pressable onPress={close}> <TouchableOpacity onPress={close} activeOpacity={0.7}>
{({ pressed }) => ( <View
<View style={{
backgroundColor: 'rgba(250,250,252,0.97)',
borderRadius: 14,
paddingVertical: 17,
paddingHorizontal: 16,
alignItems: 'center',
}}
>
<Text
style={{ style={{
backgroundColor: pressed fontSize: 20,
? 'rgba(255,255,255,0.85)' color: colors.brandOrange,
: 'rgba(250,250,252,0.97)', fontFamily: 'Nunito_700Bold',
borderRadius: 14, textAlign: 'center',
paddingVertical: 17,
paddingHorizontal: 16,
alignItems: 'center',
}} }}
> >
<Text Abbrechen
style={{ </Text>
fontSize: 20, </View>
color: colors.brandOrange, </TouchableOpacity>
fontFamily: 'Nunito_700Bold',
textAlign: 'center',
}}
>
Abbrechen
</Text>
</View>
)}
</Pressable>
</Animated.View> </Animated.View>
</Modal> </Modal>
); );

View File

@ -275,7 +275,6 @@ function PostCardImpl({ post, onCommentPress }: Props) {
hitSlop={{ top: 12, bottom: 12, left: 12, right: 12 }} hitSlop={{ top: 12, bottom: 12, left: 12, right: 12 }}
android_ripple={{ color: 'rgba(220,38,38,0.12)', borderless: true, radius: 22 }} android_ripple={{ color: 'rgba(220,38,38,0.12)', borderless: true, radius: 22 }}
className="flex-row items-center gap-1.5" className="flex-row items-center gap-1.5"
style={({ pressed }) => ({ opacity: pressed ? 0.5 : 1, transform: [{ scale: pressed ? 0.94 : 1 }] })}
> >
<Animated.View style={{ transform: [{ scale: heartScale }] }}> <Animated.View style={{ transform: [{ scale: heartScale }] }}>
<Ionicons <Ionicons
@ -294,7 +293,6 @@ function PostCardImpl({ post, onCommentPress }: Props) {
hitSlop={{ top: 12, bottom: 12, left: 12, right: 12 }} hitSlop={{ top: 12, bottom: 12, left: 12, right: 12 }}
android_ripple={{ color: 'rgba(0,0,0,0.08)', borderless: true, radius: 22 }} android_ripple={{ color: 'rgba(0,0,0,0.08)', borderless: true, radius: 22 }}
className="flex-row items-center gap-1.5" className="flex-row items-center gap-1.5"
style={({ pressed }) => ({ opacity: pressed ? 0.5 : 1, transform: [{ scale: pressed ? 0.94 : 1 }] })}
> >
<Ionicons name="chatbubble-outline" size={19} color="#737373" /> <Ionicons name="chatbubble-outline" size={19} color="#737373" />
{post.commentsCount > 0 && ( {post.commentsCount > 0 && (
@ -514,7 +512,7 @@ function DomainVoteCard({
onPress={() => onVote('yes')} onPress={() => onVote('yes')}
disabled={voting} disabled={voting}
className="flex-1 flex-row items-center justify-center gap-1.5 h-9 rounded-xl border border-rebreak-500" className="flex-1 flex-row items-center justify-center gap-1.5 h-9 rounded-xl border border-rebreak-500"
style={({ pressed }) => ({ opacity: pressed || voting ? 0.5 : 1 })} style={{ opacity: voting ? 0.5 : 1 }}
> >
<Ionicons name="thumbs-up" size={14} color="#f97316" /> <Ionicons name="thumbs-up" size={14} color="#f97316" />
<Text className="text-sm text-rebreak-500" style={{ fontFamily: 'Nunito_600SemiBold' }}> <Text className="text-sm text-rebreak-500" style={{ fontFamily: 'Nunito_600SemiBold' }}>
@ -525,7 +523,7 @@ function DomainVoteCard({
onPress={() => onVote('no')} onPress={() => onVote('no')}
disabled={voting} disabled={voting}
className="flex-1 flex-row items-center justify-center gap-1.5 h-9 rounded-xl border border-neutral-300" className="flex-1 flex-row items-center justify-center gap-1.5 h-9 rounded-xl border border-neutral-300"
style={({ pressed }) => ({ opacity: pressed || voting ? 0.5 : 1 })} style={{ opacity: voting ? 0.5 : 1 }}
> >
<Ionicons name="thumbs-down" size={14} color="#737373" /> <Ionicons name="thumbs-down" size={14} color="#737373" />
<Text className="text-sm text-neutral-500" style={{ fontFamily: 'Nunito_600SemiBold' }}> <Text className="text-sm text-neutral-500" style={{ fontFamily: 'Nunito_600SemiBold' }}>

View File

@ -6,6 +6,7 @@ import {
FlatList, FlatList,
TextInput, TextInput,
Pressable, Pressable,
TouchableOpacity,
Keyboard, Keyboard,
Platform, Platform,
ActivityIndicator, ActivityIndicator,
@ -393,12 +394,11 @@ export function PostCommentsSheet({ postId, visible, onClose }: Props) {
onSubmitEditing={submit} onSubmitEditing={submit}
blurOnSubmit={false} blurOnSubmit={false}
/> />
<Pressable <TouchableOpacity
onPress={submit} onPress={submit}
disabled={!text.trim() || submitting} disabled={!text.trim() || submitting}
style={({ pressed }) => ({ activeOpacity={0.5}
opacity: pressed || !text.trim() || submitting ? 0.5 : 1, style={{ opacity: !text.trim() || submitting ? 0.5 : 1 }}
})}
> >
<View style={{ <View style={{
width: 40, width: 40,
@ -414,7 +414,7 @@ export function PostCommentsSheet({ postId, visible, onClose }: Props) {
<Ionicons name="paper-plane" size={16} color="#fff" /> <Ionicons name="paper-plane" size={16} color="#fff" />
)} )}
</View> </View>
</Pressable> </TouchableOpacity>
</View> </View>
</Animated.View> </Animated.View>
</Animated.View> </Animated.View>

View File

@ -1,5 +1,5 @@
import { useEffect, useRef } from 'react'; import { useEffect, useRef } from 'react';
import { Modal, View, Text, Pressable, Animated, Easing } from 'react-native'; import { Modal, View, Text, TouchableOpacity, Animated, Easing } from 'react-native';
import { Ionicons } from '@expo/vector-icons'; import { Ionicons } from '@expo/vector-icons';
import { useTranslation } from 'react-i18next'; import { useTranslation } from 'react-i18next';
@ -73,7 +73,8 @@ export function SuccessAlert({ visible, title, message, onClose }: Props) {
return ( return (
<Modal visible={visible} transparent animationType="fade" onRequestClose={onClose}> <Modal visible={visible} transparent animationType="fade" onRequestClose={onClose}>
{/* Backdrop — Pressable damit Tap-outside schließt */} {/* Backdrop — Pressable damit Tap-outside schließt */}
<Pressable <TouchableOpacity
activeOpacity={1}
onPress={onClose} onPress={onClose}
style={{ style={{
flex: 1, flex: 1,
@ -85,7 +86,7 @@ export function SuccessAlert({ visible, title, message, onClose }: Props) {
> >
{/* Card Pressable mit onPress={()=>{}} damit Tap auf Card NICHT bubbelt {/* Card Pressable mit onPress={()=>{}} damit Tap auf Card NICHT bubbelt
* zum Backdrop und das Modal schließt. */} * zum Backdrop und das Modal schließt. */}
<Pressable onPress={() => {}} style={{ width: '85%', maxWidth: 320 }}> <TouchableOpacity activeOpacity={1} onPress={() => {}} style={{ width: '85%', maxWidth: 320 }}>
<Animated.View <Animated.View
style={{ style={{
backgroundColor: '#fff', backgroundColor: '#fff',
@ -149,7 +150,8 @@ export function SuccessAlert({ visible, title, message, onClose }: Props) {
</Text> </Text>
)} )}
<Pressable <TouchableOpacity
activeOpacity={0.7}
onPress={onClose} onPress={onClose}
style={{ style={{
paddingVertical: 10, paddingVertical: 10,
@ -164,10 +166,10 @@ export function SuccessAlert({ visible, title, message, onClose }: Props) {
<Text style={{ fontSize: 14, fontFamily: 'Nunito_700Bold', color: '#007AFF' }}> <Text style={{ fontSize: 14, fontFamily: 'Nunito_700Bold', color: '#007AFF' }}>
{t('common.ok')} {t('common.ok')}
</Text> </Text>
</Pressable> </TouchableOpacity>
</Animated.View> </Animated.View>
</Pressable> </TouchableOpacity>
</Pressable> </TouchableOpacity>
</Modal> </Modal>
); );
} }

View File

@ -12,7 +12,7 @@
* Für kurze Listen (3-7 items) bleibt ActionSheet besser (siehe useNativeActionSheet). * Für kurze Listen (3-7 items) bleibt ActionSheet besser (siehe useNativeActionSheet).
*/ */
import { useEffect, useState } from 'react'; import { useEffect, useState } from 'react';
import { Modal, View, Text, Pressable } from 'react-native'; import { Modal, View, Text, Pressable, TouchableOpacity } from 'react-native';
import { Picker } from '@react-native-picker/picker'; import { Picker } from '@react-native-picker/picker';
import { useColors } from '../lib/theme'; import { useColors } from '../lib/theme';
@ -91,7 +91,7 @@ export function WheelPickerModal<T extends string | number>({
borderBottomColor: colors.border, borderBottomColor: colors.border,
}} }}
> >
<Pressable onPress={onClose} hitSlop={10}> <TouchableOpacity activeOpacity={0.7} onPress={onClose} hitSlop={10}>
<Text <Text
style={{ style={{
fontSize: 15, fontSize: 15,
@ -101,7 +101,7 @@ export function WheelPickerModal<T extends string | number>({
> >
Abbrechen Abbrechen
</Text> </Text>
</Pressable> </TouchableOpacity>
<Text <Text
style={{ style={{
fontSize: 15, fontSize: 15,
@ -111,7 +111,7 @@ export function WheelPickerModal<T extends string | number>({
> >
{title} {title}
</Text> </Text>
<Pressable onPress={handleConfirm} hitSlop={10}> <TouchableOpacity activeOpacity={0.7} onPress={handleConfirm} hitSlop={10}>
<Text <Text
style={{ style={{
fontSize: 15, fontSize: 15,
@ -121,7 +121,7 @@ export function WheelPickerModal<T extends string | number>({
> >
Fertig Fertig
</Text> </Text>
</Pressable> </TouchableOpacity>
</View> </View>
{/* Wheel — native iOS UIPickerView */} {/* Wheel — native iOS UIPickerView */}

View File

@ -4,6 +4,7 @@ import {
Text, Text,
TextInput, TextInput,
Pressable, Pressable,
TouchableOpacity,
Image, Image,
ActivityIndicator, ActivityIndicator,
} from 'react-native'; } from 'react-native';
@ -262,13 +263,11 @@ export function AddDomainSheet({ visible, tier, onClose, onAdd }: Props) {
<View style={{ flex: 1 }} /> <View style={{ flex: 1 }} />
{/* Add-Button */} {/* Add-Button */}
<Pressable <TouchableOpacity
onPress={handleAdd} onPress={handleAdd}
disabled={!valid || !confirmPermanent || adding} disabled={!valid || !confirmPermanent || adding}
style={({ pressed }) => ({ activeOpacity={0.85}
opacity: pressed ? 0.85 : 1, style={{ marginBottom: insets.bottom > 0 ? 8 : 12 }}
marginBottom: insets.bottom > 0 ? 8 : 12,
})}
> >
<View <View
style={{ style={{
@ -286,7 +285,7 @@ export function AddDomainSheet({ visible, tier, onClose, onAdd }: Props) {
</Text> </Text>
)} )}
</View> </View>
</Pressable> </TouchableOpacity>
</View> </View>
</KeyboardAwareSheet> </KeyboardAwareSheet>
); );

View File

@ -1,4 +1,4 @@
import { View, Text, Pressable, ActivityIndicator } from 'react-native'; import { View, Text, TouchableOpacity, ActivityIndicator } from 'react-native';
import { Ionicons } from '@expo/vector-icons'; import { Ionicons } from '@expo/vector-icons';
import { useState } from 'react'; import { useState } from 'react';
import { useTranslation } from 'react-i18next'; import { useTranslation } from 'react-i18next';
@ -52,13 +52,12 @@ export function CooldownBanner({ remainingFormatted, onCancel }: Props) {
{remainingFormatted} {remainingFormatted}
</Text> </Text>
</View> </View>
<Pressable <TouchableOpacity
onPress={handleCancel} onPress={handleCancel}
disabled={cancelling} disabled={cancelling}
hitSlop={8} hitSlop={8}
style={({ pressed }) => ({ activeOpacity={0.7}
opacity: pressed || cancelling ? 0.7 : 1, style={{ opacity: cancelling ? 0.7 : 1 }}
})}
> >
<View style={{ <View style={{
paddingHorizontal: 12, paddingHorizontal: 12,
@ -74,7 +73,7 @@ export function CooldownBanner({ remainingFormatted, onCancel }: Props) {
</Text> </Text>
)} )}
</View> </View>
</Pressable> </TouchableOpacity>
</View> </View>
); );
} }

View File

@ -1,4 +1,4 @@
import { Modal, View, Text, Pressable, ScrollView, ActionSheetIOS, Platform, Alert } from 'react-native'; import { Modal, View, Text, Pressable, TouchableOpacity, ScrollView, ActionSheetIOS, Platform, Alert } from 'react-native';
import { Ionicons } from '@expo/vector-icons'; import { Ionicons } from '@expo/vector-icons';
import { useState } from 'react'; import { useState } from 'react';
import { useTranslation } from 'react-i18next'; import { useTranslation } from 'react-i18next';
@ -138,11 +138,9 @@ export function DeactivationExplainerSheet({
<View style={{ height: 12 }} /> <View style={{ height: 12 }} />
{/* Primary Deflector */} {/* Primary Deflector */}
<Pressable <TouchableOpacity
onPress={onBreathe} onPress={onBreathe}
style={({ pressed }) => ({ activeOpacity={0.85}
opacity: pressed ? 0.85 : 1,
})}
> >
<View style={{ <View style={{
backgroundColor: '#16a34a', backgroundColor: '#16a34a',
@ -159,18 +157,19 @@ export function DeactivationExplainerSheet({
{t('blocker.deactivation_breathe_cta')} {t('blocker.deactivation_breathe_cta')}
</Text> </Text>
</View> </View>
</Pressable> </TouchableOpacity>
{/* Destructive secondary */} {/* Destructive secondary */}
<Pressable <TouchableOpacity
onPress={showFinalConfirm} onPress={showFinalConfirm}
disabled={submitting} disabled={submitting}
hitSlop={8} hitSlop={8}
style={({ pressed }) => ({ activeOpacity={0.5}
opacity: pressed || submitting ? 0.5 : 1, style={{
opacity: submitting ? 0.5 : 1,
alignSelf: 'center', alignSelf: 'center',
paddingVertical: 12, paddingVertical: 12,
})} }}
> >
<Text <Text
style={{ style={{
@ -181,7 +180,7 @@ export function DeactivationExplainerSheet({
> >
{submitting ? t('blocker.deactivation_starting') : t('blocker.deactivation_start_anyway')} {submitting ? t('blocker.deactivation_starting') : t('blocker.deactivation_start_anyway')}
</Text> </Text>
</Pressable> </TouchableOpacity>
</ScrollView> </ScrollView>
</View> </View>
</Modal> </Modal>

View File

@ -3,6 +3,7 @@ import {
View, View,
Text, Text,
Pressable, Pressable,
TouchableOpacity,
Image, Image,
ActivityIndicator, ActivityIndicator,
} from 'react-native'; } from 'react-native';
@ -138,11 +139,9 @@ export function DomainGrid({ domains, tier, onAdd, onSubmit, onUpgradePro }: Pro
{/* Limit-Reached Upsell (nur Free) */} {/* Limit-Reached Upsell (nur Free) */}
{tier.atLimit && tier.plan === 'free' && ( {tier.atLimit && tier.plan === 'free' && (
<Pressable <TouchableOpacity
onPress={onUpgradePro} onPress={onUpgradePro}
style={({ pressed }) => ({ activeOpacity={0.85}
opacity: pressed ? 0.85 : 1,
})}
> >
<View style={{ <View style={{
backgroundColor: '#eff6ff', backgroundColor: '#eff6ff',
@ -164,7 +163,7 @@ export function DomainGrid({ domains, tier, onAdd, onSubmit, onUpgradePro }: Pro
</Text> </Text>
</View> </View>
</View> </View>
</Pressable> </TouchableOpacity>
)} )}
{/* Empty State */} {/* Empty State */}

View File

@ -1,4 +1,4 @@
import { View, Text, Switch, Pressable, ActivityIndicator } from 'react-native'; import { View, Text, Switch, TouchableOpacity, ActivityIndicator } from 'react-native';
import { Ionicons } from '@expo/vector-icons'; import { Ionicons } from '@expo/vector-icons';
import { useTranslation } from 'react-i18next'; import { useTranslation } from 'react-i18next';
import type { ProtectionState } from '../../lib/protection'; import type { ProtectionState } from '../../lib/protection';
@ -89,12 +89,10 @@ export function ProtectionCard({ state, loading, onActivate, onPressSettings }:
{loading ? ( {loading ? (
<ActivityIndicator color={iconColor} /> <ActivityIndicator color={iconColor} />
) : isActive ? ( ) : isActive ? (
<Pressable <TouchableOpacity
onPress={onPressSettings} onPress={onPressSettings}
hitSlop={10} hitSlop={10}
style={({ pressed }) => ({ activeOpacity={0.6}
opacity: pressed ? 0.6 : 1,
})}
accessibilityLabel={t('blocker.protection_settings_a11y')} accessibilityLabel={t('blocker.protection_settings_a11y')}
> >
<View style={{ <View style={{
@ -111,7 +109,7 @@ export function ProtectionCard({ state, loading, onActivate, onPressSettings }:
}}> }}>
<Ionicons name="settings-outline" size={18} color={colors.textMuted} /> <Ionicons name="settings-outline" size={18} color={colors.textMuted} />
</View> </View>
</Pressable> </TouchableOpacity>
) : ( ) : (
<Switch <Switch
value={false} value={false}

View File

@ -4,6 +4,7 @@ import {
View, View,
Text, Text,
Pressable, Pressable,
TouchableOpacity,
ScrollView, ScrollView,
Dimensions, Dimensions,
Animated, Animated,
@ -343,13 +344,11 @@ export function ProtectionDetailsSheet({
))} ))}
</View> </View>
{/* MEHR INFO outline button: Pressable=card, inner View=flex-row */} {/* MEHR INFO outline button: TouchableOpacity=card, inner View=flex-row */}
<Pressable <TouchableOpacity
onPress={onRequestDeactivation} onPress={onRequestDeactivation}
style={({ pressed }) => ({ activeOpacity={0.75}
marginTop: 4, style={{ marginTop: 4 }}
opacity: pressed ? 0.75 : 1,
})}
> >
<View style={{ <View style={{
paddingVertical: 14, paddingVertical: 14,
@ -368,7 +367,7 @@ export function ProtectionDetailsSheet({
{t('blocker.more_info_title')} {t('blocker.more_info_title')}
</Text> </Text>
</View> </View>
</Pressable> </TouchableOpacity>
</ScrollView> </ScrollView>
</Animated.View> </Animated.View>
</Animated.View> </Animated.View>
@ -676,11 +675,9 @@ function FaqItem({ question, answer }: { question: string; answer: string }) {
backgroundColor: colors.bg, backgroundColor: colors.bg,
}} }}
> >
<Pressable <TouchableOpacity
onPress={() => setOpen((v) => !v)} onPress={() => setOpen((v) => !v)}
style={({ pressed }) => ({ activeOpacity={0.75}
opacity: pressed ? 0.75 : 1,
})}
> >
<View style={{ flexDirection: 'row', alignItems: 'center', paddingHorizontal: 14, paddingVertical: 14 }}> <View style={{ flexDirection: 'row', alignItems: 'center', paddingHorizontal: 14, paddingVertical: 14 }}>
<View style={{ flex: 1, paddingRight: 12 }}> <View style={{ flex: 1, paddingRight: 12 }}>
@ -702,7 +699,7 @@ function FaqItem({ question, answer }: { question: string; answer: string }) {
<Ionicons name="chevron-down" size={16} color={colors.textMuted} /> <Ionicons name="chevron-down" size={16} color={colors.textMuted} />
</Animated.View> </Animated.View>
</View> </View>
</Pressable> </TouchableOpacity>
{open && ( {open && (
<View style={{ paddingHorizontal: 14, paddingBottom: 14, paddingTop: 0 }}> <View style={{ paddingHorizontal: 14, paddingBottom: 14, paddingTop: 0 }}>
<Text style={{ fontSize: 13, fontFamily: 'Nunito_400Regular', color: colors.textMuted, lineHeight: 19 }}> <Text style={{ fontSize: 13, fontFamily: 'Nunito_400Regular', color: colors.textMuted, lineHeight: 19 }}>

View File

@ -1,4 +1,4 @@
import { View, Text, Pressable } from 'react-native'; import { View, Text, TouchableOpacity } from 'react-native';
import { Ionicons } from '@expo/vector-icons'; import { Ionicons } from '@expo/vector-icons';
import { useTranslation } from 'react-i18next'; import { useTranslation } from 'react-i18next';
import type { ProtectionState } from '../../lib/protection'; import type { ProtectionState } from '../../lib/protection';
@ -74,12 +74,10 @@ export function ProtectionLockedCard({ state, onPressSettings }: Props) {
</Text> </Text>
</View> </View>
<Pressable <TouchableOpacity
onPress={onPressSettings} onPress={onPressSettings}
hitSlop={10} hitSlop={10}
style={({ pressed }) => ({ activeOpacity={0.6}
opacity: pressed ? 0.6 : 1,
})}
accessibilityLabel={t('blocker.protection_settings_a11y')} accessibilityLabel={t('blocker.protection_settings_a11y')}
> >
<View style={{ <View style={{
@ -96,7 +94,7 @@ export function ProtectionLockedCard({ state, onPressSettings }: Props) {
}}> }}>
<Ionicons name="settings-outline" size={18} color={colors.textMuted} /> <Ionicons name="settings-outline" size={18} color={colors.textMuted} />
</View> </View>
</Pressable> </TouchableOpacity>
</View> </View>
{/* Stats nur wenn aktiv und kein Cooldown */} {/* Stats nur wenn aktiv und kein Cooldown */}

View File

@ -3,6 +3,7 @@ import {
View, View,
Text, Text,
Pressable, Pressable,
TouchableOpacity,
Image, Image,
StyleSheet, StyleSheet,
Modal, Modal,
@ -127,10 +128,11 @@ export function ChatBubble({
> >
{/* Reply preview */} {/* Reply preview */}
{msg.replyTo && ( {msg.replyTo && (
<Pressable <TouchableOpacity
onPress={() => { onPress={() => {
/* could implement scroll-to */ /* could implement scroll-to */
}} }}
activeOpacity={0.7}
style={[ style={[
styles.replyPreview, styles.replyPreview,
{ {
@ -163,13 +165,14 @@ export function ChatBubble({
)}{' '} )}{' '}
{msg.replyTo.content || (replyHasAttachment ? t('chat.image_attachment') : '…')} {msg.replyTo.content || (replyHasAttachment ? t('chat.image_attachment') : '…')}
</Text> </Text>
</Pressable> </TouchableOpacity>
)} )}
{/* Image attachment */} {/* Image attachment */}
{msg.attachmentUrl && msg.attachmentType === 'image' && ( {msg.attachmentUrl && msg.attachmentType === 'image' && (
<Pressable <TouchableOpacity
onPress={() => onOpenImage(msg.attachmentUrl!)} onPress={() => onOpenImage(msg.attachmentUrl!)}
activeOpacity={0.7}
style={[styles.imageWrap, msg.content ? { marginBottom: 4 } : null]} style={[styles.imageWrap, msg.content ? { marginBottom: 4 } : null]}
> >
<Image <Image
@ -190,7 +193,7 @@ export function ChatBubble({
<Text style={{ fontSize: 10, color: '#fff' }}>{formatTime(msg.createdAt)}</Text> <Text style={{ fontSize: 10, color: '#fff' }}>{formatTime(msg.createdAt)}</Text>
</View> </View>
)} )}
</Pressable> </TouchableOpacity>
)} )}
{/* File attachment */} {/* File attachment */}
@ -284,25 +287,27 @@ export function ChatBubble({
animationType="fade" animationType="fade"
onRequestClose={() => setActionsOpen(false)} onRequestClose={() => setActionsOpen(false)}
> >
<Pressable style={styles.sheetBackdrop} onPress={() => setActionsOpen(false)}> <TouchableOpacity style={styles.sheetBackdrop} onPress={() => setActionsOpen(false)} activeOpacity={1}>
<Pressable style={styles.sheet} onPress={() => {}}> <Pressable style={styles.sheet} onPress={() => {}}>
<View style={styles.sheetGrabber} /> <View style={styles.sheetGrabber} />
<Pressable <TouchableOpacity
style={styles.sheetItem} style={styles.sheetItem}
onPress={() => { onPress={() => {
setActionsOpen(false); setActionsOpen(false);
onReply(msg); onReply(msg);
}} }}
activeOpacity={0.7}
> >
<Ionicons name="arrow-undo" size={18} color="#007AFF" /> <Ionicons name="arrow-undo" size={18} color="#007AFF" />
<Text style={styles.sheetText}>{t('chat.reply')}</Text> <Text style={styles.sheetText}>{t('chat.reply')}</Text>
</Pressable> </TouchableOpacity>
<Pressable <TouchableOpacity
style={styles.sheetItem} style={styles.sheetItem}
onPress={() => { onPress={() => {
setActionsOpen(false); setActionsOpen(false);
onLike(msg); onLike(msg);
}} }}
activeOpacity={0.7}
> >
<Ionicons <Ionicons
name={msg.likedByMe ? 'heart' : 'heart-outline'} name={msg.likedByMe ? 'heart' : 'heart-outline'}
@ -312,15 +317,15 @@ export function ChatBubble({
<Text style={styles.sheetText}> <Text style={styles.sheetText}>
{msg.likedByMe ? t('chat.unlike') : t('chat.like')} {msg.likedByMe ? t('chat.unlike') : t('chat.like')}
</Text> </Text>
</Pressable> </TouchableOpacity>
{msg.content !== '' && ( {msg.content !== '' && (
<Pressable style={styles.sheetItem} onPress={copyContent}> <TouchableOpacity style={styles.sheetItem} onPress={copyContent} activeOpacity={0.7}>
<Ionicons name="copy-outline" size={18} color="#007AFF" /> <Ionicons name="copy-outline" size={18} color="#007AFF" />
<Text style={styles.sheetText}>{t('chat.copy')}</Text> <Text style={styles.sheetText}>{t('chat.copy')}</Text>
</Pressable> </TouchableOpacity>
)} )}
</Pressable> </Pressable>
</Pressable> </TouchableOpacity>
</Modal> </Modal>
</> </>
); );

View File

@ -3,7 +3,7 @@ import {
View, View,
Text, Text,
TextInput, TextInput,
Pressable, TouchableOpacity,
Image, Image,
StyleSheet, StyleSheet,
ActivityIndicator, ActivityIndicator,
@ -155,9 +155,9 @@ export function ChatInput({
{replyTo.content || '…'} {replyTo.content || '…'}
</Text> </Text>
</View> </View>
<Pressable hitSlop={10} onPress={onCancelReply}> <TouchableOpacity hitSlop={10} onPress={onCancelReply} activeOpacity={0.7}>
<Ionicons name="close" size={16} color="#737373" /> <Ionicons name="close" size={16} color="#737373" />
</Pressable> </TouchableOpacity>
</View> </View>
)} )}
@ -174,21 +174,22 @@ export function ChatInput({
<Text style={styles.attachName} numberOfLines={1}> <Text style={styles.attachName} numberOfLines={1}>
{attachment.name} {attachment.name}
</Text> </Text>
<Pressable hitSlop={10} onPress={clearAttachment}> <TouchableOpacity hitSlop={10} onPress={clearAttachment} activeOpacity={0.7}>
<Ionicons name="close" size={16} color="#737373" /> <Ionicons name="close" size={16} color="#737373" />
</Pressable> </TouchableOpacity>
</View> </View>
)} )}
{/* Input row */} {/* Input row */}
<View style={styles.row}> <View style={styles.row}>
<Pressable <TouchableOpacity
activeOpacity={0.7}
style={styles.iconBtn} style={styles.iconBtn}
onPress={pickImage} onPress={pickImage}
disabled={uploading || sending || disabled} disabled={uploading || sending || disabled}
> >
<Ionicons name="image-outline" size={22} color="#737373" /> <Ionicons name="image-outline" size={22} color="#737373" />
</Pressable> </TouchableOpacity>
<View style={styles.inputWrap}> <View style={styles.inputWrap}>
<TextInput <TextInput
@ -204,7 +205,8 @@ export function ChatInput({
/> />
</View> </View>
<Pressable <TouchableOpacity
activeOpacity={0.7}
onPress={handleSend} onPress={handleSend}
disabled={!hasContent || sending || uploading || disabled} disabled={!hasContent || sending || uploading || disabled}
style={[ style={[
@ -217,7 +219,7 @@ export function ChatInput({
) : ( ) : (
<Ionicons name="send" size={16} color={hasContent ? '#fff' : '#a3a3a3'} /> <Ionicons name="send" size={16} color={hasContent ? '#fff' : '#a3a3a3'} />
)} )}
</Pressable> </TouchableOpacity>
</View> </View>
</View> </View>
); );

View File

@ -3,7 +3,7 @@ import {
View, View,
Text, Text,
TextInput, TextInput,
Pressable, TouchableOpacity,
StyleSheet, StyleSheet,
ActivityIndicator, ActivityIndicator,
} from 'react-native'; } from 'react-native';
@ -96,12 +96,12 @@ export function CreateRoomSheet({ visible, onClose, onCreated }: Props) {
/> />
{/* Public toggle */} {/* Public toggle */}
<Pressable style={styles.toggleRow} onPress={() => setIsPublic((v) => !v)}> <TouchableOpacity activeOpacity={0.7} style={styles.toggleRow} onPress={() => setIsPublic((v) => !v)}>
<Text style={styles.toggleLabel}>{t('chat.public_room')}</Text> <Text style={styles.toggleLabel}>{t('chat.public_room')}</Text>
<View style={[styles.toggle, isPublic && styles.toggleOn]}> <View style={[styles.toggle, isPublic && styles.toggleOn]}>
<View style={[styles.toggleKnob, isPublic && styles.toggleKnobOn]} /> <View style={[styles.toggleKnob, isPublic && styles.toggleKnobOn]} />
</View> </View>
</Pressable> </TouchableOpacity>
{/* Join mode (private only) */} {/* Join mode (private only) */}
{!isPublic && ( {!isPublic && (
@ -109,8 +109,9 @@ export function CreateRoomSheet({ visible, onClose, onCreated }: Props) {
<Text style={styles.subLabel}>{t('chat.join_mode')}</Text> <Text style={styles.subLabel}>{t('chat.join_mode')}</Text>
<View style={styles.modeRow}> <View style={styles.modeRow}>
{(['approval', 'invite_only'] as const).map((mode) => ( {(['approval', 'invite_only'] as const).map((mode) => (
<Pressable <TouchableOpacity
key={mode} key={mode}
activeOpacity={0.7}
style={[styles.modeBtn, joinMode === mode && styles.modeBtnActive]} style={[styles.modeBtn, joinMode === mode && styles.modeBtnActive]}
onPress={() => setJoinMode(mode)} onPress={() => setJoinMode(mode)}
> >
@ -122,7 +123,7 @@ export function CreateRoomSheet({ visible, onClose, onCreated }: Props) {
> >
{t(`chat.join_mode_${mode === 'approval' ? 'approval' : 'invite'}`)} {t(`chat.join_mode_${mode === 'approval' ? 'approval' : 'invite'}`)}
</Text> </Text>
</Pressable> </TouchableOpacity>
))} ))}
</View> </View>
</View> </View>
@ -132,10 +133,11 @@ export function CreateRoomSheet({ visible, onClose, onCreated }: Props) {
{/* Actions */} {/* Actions */}
<View style={styles.actions}> <View style={styles.actions}>
<Pressable onPress={handleClose} style={styles.cancelBtn}> <TouchableOpacity activeOpacity={0.7} onPress={handleClose} style={styles.cancelBtn}>
<Text style={styles.cancelText}>{t('common.cancel')}</Text> <Text style={styles.cancelText}>{t('common.cancel')}</Text>
</Pressable> </TouchableOpacity>
<Pressable <TouchableOpacity
activeOpacity={0.7}
onPress={create} onPress={create}
disabled={!name.trim() || creating} disabled={!name.trim() || creating}
style={[styles.createBtn, { opacity: !name.trim() || creating ? 0.5 : 1 }]} style={[styles.createBtn, { opacity: !name.trim() || creating ? 0.5 : 1 }]}
@ -145,7 +147,7 @@ export function CreateRoomSheet({ visible, onClose, onCreated }: Props) {
) : ( ) : (
<Text style={styles.createText}>{t('chat.create')}</Text> <Text style={styles.createText}>{t('chat.create')}</Text>
)} )}
</Pressable> </TouchableOpacity>
</View> </View>
</View> </View>
</KeyboardAwareSheet> </KeyboardAwareSheet>

View File

@ -40,8 +40,7 @@ export function RoomCard({ room, onPress }: Props) {
return ( return (
<Pressable onPress={onPress} android_ripple={{ color: '#f5f5f5' }}> <Pressable onPress={onPress} android_ripple={{ color: '#f5f5f5' }}>
{({ pressed }) => ( <View style={styles.row}>
<View style={[styles.row, { opacity: pressed ? 0.7 : 1 }]}>
<View <View
style={[ style={[
styles.avatar, styles.avatar,
@ -99,8 +98,7 @@ export function RoomCard({ room, onPress }: Props) {
)} )}
</View> </View>
</View> </View>
</View> </View>
)}
</Pressable> </Pressable>
); );
} }

View File

@ -2,7 +2,7 @@ import {
ActivityIndicator, ActivityIndicator,
Alert, Alert,
Linking, Linking,
Pressable, TouchableOpacity,
Text, Text,
TextInput, TextInput,
View, View,
@ -140,13 +140,13 @@ export function AddMacSheet({
? t('devices.download_button') ? t('devices.download_button')
: t('devices.success_title')} : t('devices.success_title')}
</Text> </Text>
<Pressable <TouchableOpacity
onPress={handleClose} onPress={handleClose}
hitSlop={8} hitSlop={8}
style={({ pressed }) => ({ opacity: pressed ? 0.5 : 1 })} activeOpacity={0.5}
> >
<Ionicons name="close" size={22} color={colors.textMuted} /> <Ionicons name="close" size={22} color={colors.textMuted} />
</Pressable> </TouchableOpacity>
</View> </View>
} }
> >
@ -221,16 +221,17 @@ function Step1LabelContent({
</Text> </Text>
) : null} ) : null}
<Pressable <TouchableOpacity
onPress={onPrepare} onPress={onPrepare}
disabled={enrolling} disabled={enrolling}
style={({ pressed }) => ({ activeOpacity={0.7}
style={{
backgroundColor: colors.brandOrange, backgroundColor: colors.brandOrange,
borderRadius: 14, borderRadius: 14,
paddingVertical: 16, paddingVertical: 16,
alignItems: 'center', alignItems: 'center',
opacity: pressed || enrolling ? 0.7 : 1, opacity: enrolling ? 0.7 : 1,
})} }}
> >
{enrolling ? ( {enrolling ? (
<ActivityIndicator color="#fff" /> <ActivityIndicator color="#fff" />
@ -239,7 +240,7 @@ function Step1LabelContent({
{t('devices.prepare_profile')} {t('devices.prepare_profile')}
</Text> </Text>
)} )}
</Pressable> </TouchableOpacity>
</View> </View>
); );
} }
@ -330,9 +331,10 @@ function Step2OnboardingContent({
</View> </View>
{/* Download button */} {/* Download button */}
<Pressable <TouchableOpacity
onPress={onDownload} onPress={onDownload}
style={({ pressed }) => ({ activeOpacity={0.7}
style={{
backgroundColor: colors.brandOrange, backgroundColor: colors.brandOrange,
borderRadius: 14, borderRadius: 14,
paddingVertical: 16, paddingVertical: 16,
@ -340,27 +342,27 @@ function Step2OnboardingContent({
flexDirection: 'row', flexDirection: 'row',
justifyContent: 'center', justifyContent: 'center',
gap: 8, gap: 8,
opacity: pressed ? 0.7 : 1, }}
})}
> >
<Ionicons name="download-outline" size={18} color="#fff" /> <Ionicons name="download-outline" size={18} color="#fff" />
<Text style={{ fontSize: 16, color: '#fff', fontFamily: 'Nunito_700Bold' }}> <Text style={{ fontSize: 16, color: '#fff', fontFamily: 'Nunito_700Bold' }}>
{t('devices.download_button')} {t('devices.download_button')}
</Text> </Text>
</Pressable> </TouchableOpacity>
{/* Confirm installed */} {/* Confirm installed */}
<Pressable <TouchableOpacity
onPress={onConfirmInstalled} onPress={onConfirmInstalled}
disabled={confirming} disabled={confirming}
style={({ pressed }) => ({ activeOpacity={0.7}
style={{
borderWidth: 1.5, borderWidth: 1.5,
borderColor: colors.brandOrange, borderColor: colors.brandOrange,
borderRadius: 14, borderRadius: 14,
paddingVertical: 14, paddingVertical: 14,
alignItems: 'center', alignItems: 'center',
opacity: pressed || confirming ? 0.7 : 1, opacity: confirming ? 0.7 : 1,
})} }}
> >
{confirming ? ( {confirming ? (
<ActivityIndicator color={colors.brandOrange} /> <ActivityIndicator color={colors.brandOrange} />
@ -369,12 +371,13 @@ function Step2OnboardingContent({
{t('devices.confirm_installed')} {t('devices.confirm_installed')}
</Text> </Text>
)} )}
</Pressable> </TouchableOpacity>
{/* Need help */} {/* Need help */}
<Pressable <TouchableOpacity
onPress={onNeedHelp} onPress={onNeedHelp}
style={({ pressed }) => ({ opacity: pressed ? 0.5 : 1, alignItems: 'center' })} activeOpacity={0.5}
style={{ alignItems: 'center' }}
> >
<Text <Text
style={{ style={{
@ -386,7 +389,7 @@ function Step2OnboardingContent({
> >
{t('devices.need_help')} {t('devices.need_help')}
</Text> </Text>
</Pressable> </TouchableOpacity>
</View> </View>
); );
} }
@ -436,22 +439,22 @@ function Step3SuccessContent({
</Text> </Text>
</View> </View>
<Pressable <TouchableOpacity
onPress={onClose} onPress={onClose}
style={({ pressed }) => ({ activeOpacity={0.7}
style={{
backgroundColor: colors.brandOrange, backgroundColor: colors.brandOrange,
borderRadius: 14, borderRadius: 14,
paddingVertical: 16, paddingVertical: 16,
paddingHorizontal: 40, paddingHorizontal: 40,
alignItems: 'center', alignItems: 'center',
opacity: pressed ? 0.7 : 1,
alignSelf: 'stretch', alignSelf: 'stretch',
})} }}
> >
<Text style={{ fontSize: 16, color: '#fff', fontFamily: 'Nunito_700Bold' }}> <Text style={{ fontSize: 16, color: '#fff', fontFamily: 'Nunito_700Bold' }}>
{t('common.ok')} {t('common.ok')}
</Text> </Text>
</Pressable> </TouchableOpacity>
</View> </View>
); );
} }

View File

@ -2,7 +2,7 @@ import {
ActivityIndicator, ActivityIndicator,
Alert, Alert,
Linking, Linking,
Pressable, TouchableOpacity,
Text, Text,
TextInput, TextInput,
View, View,
@ -141,13 +141,13 @@ export function AddWindowsSheet({
? t('devices.windows_download_button') ? t('devices.windows_download_button')
: t('devices.windows_success_title')} : t('devices.windows_success_title')}
</Text> </Text>
<Pressable <TouchableOpacity
onPress={handleClose} onPress={handleClose}
hitSlop={8} hitSlop={8}
style={({ pressed }) => ({ opacity: pressed ? 0.5 : 1 })} activeOpacity={0.5}
> >
<Ionicons name="close" size={22} color={colors.textMuted} /> <Ionicons name="close" size={22} color={colors.textMuted} />
</Pressable> </TouchableOpacity>
</View> </View>
} }
> >
@ -228,16 +228,17 @@ function WindowsStep1LabelContent({
</Text> </Text>
) : null} ) : null}
<Pressable <TouchableOpacity
onPress={onPrepare} onPress={onPrepare}
disabled={enrolling} disabled={enrolling}
style={({ pressed }) => ({ activeOpacity={0.7}
style={{
backgroundColor: colors.brandOrange, backgroundColor: colors.brandOrange,
borderRadius: 14, borderRadius: 14,
paddingVertical: 16, paddingVertical: 16,
alignItems: 'center', alignItems: 'center',
opacity: pressed || enrolling ? 0.7 : 1, opacity: enrolling ? 0.7 : 1,
})} }}
> >
{enrolling ? ( {enrolling ? (
<ActivityIndicator color="#fff" /> <ActivityIndicator color="#fff" />
@ -246,7 +247,7 @@ function WindowsStep1LabelContent({
{t('devices.prepare_profile')} {t('devices.prepare_profile')}
</Text> </Text>
)} )}
</Pressable> </TouchableOpacity>
</View> </View>
); );
} }
@ -337,9 +338,10 @@ function WindowsStep2OnboardingContent({
</View> </View>
{/* Download button */} {/* Download button */}
<Pressable <TouchableOpacity
onPress={onDownload} onPress={onDownload}
style={({ pressed }) => ({ activeOpacity={0.7}
style={{
backgroundColor: colors.brandOrange, backgroundColor: colors.brandOrange,
borderRadius: 14, borderRadius: 14,
paddingVertical: 16, paddingVertical: 16,
@ -347,27 +349,27 @@ function WindowsStep2OnboardingContent({
flexDirection: 'row', flexDirection: 'row',
justifyContent: 'center', justifyContent: 'center',
gap: 8, gap: 8,
opacity: pressed ? 0.7 : 1, }}
})}
> >
<Ionicons name="download-outline" size={18} color="#fff" /> <Ionicons name="download-outline" size={18} color="#fff" />
<Text style={{ fontSize: 16, color: '#fff', fontFamily: 'Nunito_700Bold' }}> <Text style={{ fontSize: 16, color: '#fff', fontFamily: 'Nunito_700Bold' }}>
{t('devices.windows_download_button')} {t('devices.windows_download_button')}
</Text> </Text>
</Pressable> </TouchableOpacity>
{/* Confirm installed */} {/* Confirm installed */}
<Pressable <TouchableOpacity
onPress={onConfirmInstalled} onPress={onConfirmInstalled}
disabled={confirming} disabled={confirming}
style={({ pressed }) => ({ activeOpacity={0.7}
style={{
borderWidth: 1.5, borderWidth: 1.5,
borderColor: colors.brandOrange, borderColor: colors.brandOrange,
borderRadius: 14, borderRadius: 14,
paddingVertical: 14, paddingVertical: 14,
alignItems: 'center', alignItems: 'center',
opacity: pressed || confirming ? 0.7 : 1, opacity: confirming ? 0.7 : 1,
})} }}
> >
{confirming ? ( {confirming ? (
<ActivityIndicator color={colors.brandOrange} /> <ActivityIndicator color={colors.brandOrange} />
@ -376,12 +378,13 @@ function WindowsStep2OnboardingContent({
{t('devices.confirm_installed')} {t('devices.confirm_installed')}
</Text> </Text>
)} )}
</Pressable> </TouchableOpacity>
{/* Need help */} {/* Need help */}
<Pressable <TouchableOpacity
onPress={onNeedHelp} onPress={onNeedHelp}
style={({ pressed }) => ({ opacity: pressed ? 0.5 : 1, alignItems: 'center' })} activeOpacity={0.5}
style={{ alignItems: 'center' }}
> >
<Text <Text
style={{ style={{
@ -393,7 +396,7 @@ function WindowsStep2OnboardingContent({
> >
{t('devices.need_help')} {t('devices.need_help')}
</Text> </Text>
</Pressable> </TouchableOpacity>
</View> </View>
); );
} }
@ -443,22 +446,22 @@ function WindowsStep3SuccessContent({
</Text> </Text>
</View> </View>
<Pressable <TouchableOpacity
onPress={onClose} onPress={onClose}
style={({ pressed }) => ({ activeOpacity={0.7}
style={{
backgroundColor: colors.brandOrange, backgroundColor: colors.brandOrange,
borderRadius: 14, borderRadius: 14,
paddingVertical: 16, paddingVertical: 16,
paddingHorizontal: 40, paddingHorizontal: 40,
alignItems: 'center', alignItems: 'center',
opacity: pressed ? 0.7 : 1,
alignSelf: 'stretch', alignSelf: 'stretch',
})} }}
> >
<Text style={{ fontSize: 16, color: '#fff', fontFamily: 'Nunito_700Bold' }}> <Text style={{ fontSize: 16, color: '#fff', fontFamily: 'Nunito_700Bold' }}>
{t('common.ok')} {t('common.ok')}
</Text> </Text>
</Pressable> </TouchableOpacity>
</View> </View>
); );
} }

View File

@ -1,6 +1,6 @@
// RN-Port der Vue-Card aus apps/rebreak/app/components/urge/UrgeGamePicker.vue // RN-Port der Vue-Card aus apps/rebreak/app/components/urge/UrgeGamePicker.vue
// 2x2-Grid-Kachel mit SVG-Icon (56x56), Titel, descKey und Star-Rating. // 2x2-Grid-Kachel mit SVG-Icon (56x56), Titel, descKey und Star-Rating.
import { View, Text, Pressable } from 'react-native'; import { View, Text, TouchableOpacity } from 'react-native';
import { SvgXml } from 'react-native-svg'; import { SvgXml } from 'react-native-svg';
import { useTranslation } from 'react-i18next'; import { useTranslation } from 'react-i18next';
import { GameRatingStars } from './GameRatingStars'; import { GameRatingStars } from './GameRatingStars';
@ -27,13 +27,10 @@ export function GameCard({
}: GameCardProps) { }: GameCardProps) {
const { t } = useTranslation(); const { t } = useTranslation();
return ( return (
<Pressable <TouchableOpacity
onPress={() => onPress(id)} onPress={() => onPress(id)}
style={({ pressed }) => ({ activeOpacity={0.85}
width: '100%', style={{ width: '100%' }}
transform: [{ scale: pressed ? 0.97 : 1 }],
opacity: pressed ? 0.85 : 1,
})}
> >
<View style={{ <View style={{
borderRadius: 18, borderRadius: 18,
@ -76,6 +73,6 @@ export function GameCard({
</View> </View>
</View> </View>
</View> </View>
</Pressable> </TouchableOpacity>
); );
} }

View File

@ -1,6 +1,6 @@
// RN-Port von apps/rebreak/app/components/StarRating.vue // RN-Port von apps/rebreak/app/components/StarRating.vue
// Unterstützt fractional values (z.B. 3.7) via width-clipping. // Unterstützt fractional values (z.B. 3.7) via width-clipping.
import { View, Pressable } from 'react-native'; import { View, TouchableOpacity } from 'react-native';
import { Ionicons } from '@expo/vector-icons'; import { Ionicons } from '@expo/vector-icons';
import { useState } from 'react'; import { useState } from 'react';
@ -83,15 +83,14 @@ export function StarRating({
if (interactive) { if (interactive) {
stars.push( stars.push(
<Pressable <TouchableOpacity
key={`press-${i}`} key={`press-${i}`}
onPress={() => onChange?.(i)} onPress={() => onChange?.(i)}
onHoverIn={() => setHover(i)}
onHoverOut={() => setHover(0)}
hitSlop={4} hitSlop={4}
activeOpacity={0.7}
> >
{star} {star}
</Pressable> </TouchableOpacity>
); );
} else { } else {
stars.push(star); stars.push(star);

View File

@ -1,4 +1,4 @@
import { View, Text, Pressable, Modal } from 'react-native'; import { View, Text, Pressable, TouchableOpacity, Modal } from 'react-native';
import { useRouter, type RelativePathString } from 'expo-router'; import { useRouter, type RelativePathString } from 'expo-router';
import { Ionicons } from '@expo/vector-icons'; import { Ionicons } from '@expo/vector-icons';
import { useTranslation } from 'react-i18next'; import { useTranslation } from 'react-i18next';
@ -166,14 +166,13 @@ export function HeaderDropdownMenu({ visible, onClose, topOffset = 80 }: Props)
{/* Profile · Settings · Games · [Debug DEV] */} {/* Profile · Settings · Games · [Debug DEV] */}
{items.map((item) => ( {items.map((item) => (
<Pressable <TouchableOpacity
key={item.key} key={item.key}
onPress={() => { onPress={() => {
onClose(); onClose();
void item.onSelect(); void item.onSelect();
}} }}
android_ripple={{ color: colors.surfaceElevated }} activeOpacity={0.7}
style={({ pressed }) => ({ opacity: pressed ? 0.7 : 1 })}
> >
<View <View
style={{ style={{
@ -199,16 +198,15 @@ export function HeaderDropdownMenu({ visible, onClose, topOffset = 80 }: Props)
{item.label} {item.label}
</Text> </Text>
</View> </View>
</Pressable> </TouchableOpacity>
))} ))}
<View style={{ height: 1, backgroundColor: colors.border }} /> <View style={{ height: 1, backgroundColor: colors.border }} />
{/* Abmelden — neutral, nicht rot */} {/* Abmelden — neutral, nicht rot */}
<Pressable <TouchableOpacity
onPress={handleLogout} onPress={handleLogout}
android_ripple={{ color: colors.surfaceElevated }} activeOpacity={0.7}
style={({ pressed }) => ({ opacity: pressed ? 0.7 : 1 })}
> >
<View <View
style={{ style={{
@ -234,7 +232,7 @@ export function HeaderDropdownMenu({ visible, onClose, topOffset = 80 }: Props)
{t('headerMenu.logout')} {t('headerMenu.logout')}
</Text> </Text>
</View> </View>
</Pressable> </TouchableOpacity>
</View> </View>
</Pressable> </Pressable>
</Modal> </Modal>

View File

@ -2,7 +2,7 @@ import { useState } from 'react';
import { import {
ActivityIndicator, ActivityIndicator,
Linking, Linking,
Pressable, TouchableOpacity,
ScrollView, ScrollView,
Text, Text,
TextInput, TextInput,
@ -164,17 +164,17 @@ export function ConnectMailSheet({ visible, onClose, onSuccess }: Props) {
}} }}
> >
{view === 'form' ? ( {view === 'form' ? (
<Pressable onPress={handleBack} hitSlop={10}> <TouchableOpacity activeOpacity={0.7} onPress={handleBack} hitSlop={10}>
<Text style={{ fontSize: 16, fontFamily: 'Nunito_400Regular', color: colors.textMuted }}> <Text style={{ fontSize: 16, fontFamily: 'Nunito_400Regular', color: colors.textMuted }}>
{t('common.back')} {t('common.back')}
</Text> </Text>
</Pressable> </TouchableOpacity>
) : ( ) : (
<Pressable onPress={handleClose} hitSlop={10}> <TouchableOpacity activeOpacity={0.7} onPress={handleClose} hitSlop={10}>
<Text style={{ fontSize: 16, fontFamily: 'Nunito_400Regular', color: colors.textMuted }}> <Text style={{ fontSize: 16, fontFamily: 'Nunito_400Regular', color: colors.textMuted }}>
{t('common.cancel')} {t('common.cancel')}
</Text> </Text>
</Pressable> </TouchableOpacity>
)} )}
<Text style={{ fontSize: 16, fontFamily: 'Nunito_700Bold', color: colors.text }}> <Text style={{ fontSize: 16, fontFamily: 'Nunito_700Bold', color: colors.text }}>
{view === 'form' && currentProvider {view === 'form' && currentProvider
@ -250,13 +250,11 @@ function ProviderGrid({
<View style={{ flexDirection: 'row', flexWrap: 'wrap', gap: 10 }}> <View style={{ flexDirection: 'row', flexWrap: 'wrap', gap: 10 }}>
{providers.map((p) => ( {providers.map((p) => (
<Pressable <TouchableOpacity
key={p.id} key={p.id}
onPress={() => onSelect(p)} onPress={() => onSelect(p)}
style={({ pressed }) => ({ activeOpacity={0.7}
width: '47%', style={{ width: '47%' }}
opacity: pressed ? 0.7 : 1,
})}
> >
<View style={{ <View style={{
flexDirection: 'row', flexDirection: 'row',
@ -290,7 +288,7 @@ function ProviderGrid({
</View> </View>
<Ionicons name="chevron-forward" size={14} color={colors.border} /> <Ionicons name="chevron-forward" size={14} color={colors.border} />
</View> </View>
</Pressable> </TouchableOpacity>
))} ))}
</View> </View>
</ScrollView> </ScrollView>
@ -376,7 +374,7 @@ function FormView({
{t(provider.guideKey)} {t(provider.guideKey)}
</Text> </Text>
{provider.guideUrl.length > 0 && ( {provider.guideUrl.length > 0 && (
<Pressable onPress={() => Linking.openURL(provider.guideUrl)}> <TouchableOpacity activeOpacity={0.7} onPress={() => Linking.openURL(provider.guideUrl)}>
<Text <Text
style={{ style={{
fontSize: 12, fontSize: 12,
@ -387,7 +385,7 @@ function FormView({
> >
{t('mail.app_password_open_link')} {t('mail.app_password_open_link')}
</Text> </Text>
</Pressable> </TouchableOpacity>
)} )}
</View> </View>
</View> </View>
@ -460,7 +458,8 @@ function FormView({
color: colors.text, color: colors.text,
}} }}
/> />
<Pressable <TouchableOpacity
activeOpacity={0.7}
onPress={onTogglePasswordVisible} onPress={onTogglePasswordVisible}
hitSlop={8} hitSlop={8}
style={{ style={{
@ -476,7 +475,7 @@ function FormView({
size={20} size={20}
color="#a3a3a3" color="#a3a3a3"
/> />
</Pressable> </TouchableOpacity>
</View> </View>
</View> </View>
@ -520,14 +519,11 @@ function FormView({
)} )}
{/* Connect-Button */} {/* Connect-Button */}
<Pressable <TouchableOpacity
activeOpacity={0.85}
onPress={onConnect} onPress={onConnect}
disabled={!canConnect} disabled={!canConnect}
style={({ pressed }) => ({ style={{ marginTop: 4, marginBottom: insets.bottom > 0 ? 8 : 12 }}
opacity: pressed ? 0.85 : 1,
marginTop: 4,
marginBottom: insets.bottom > 0 ? 8 : 12,
})}
> >
<View style={{ <View style={{
backgroundColor: canConnect ? '#007AFF' : '#d4d4d4', backgroundColor: canConnect ? '#007AFF' : '#d4d4d4',
@ -543,7 +539,7 @@ function FormView({
</Text> </Text>
)} )}
</View> </View>
</Pressable> </TouchableOpacity>
</ScrollView> </ScrollView>
); );
} }

View File

@ -1,5 +1,5 @@
import { useState } from 'react'; import { useState } from 'react';
import { ActivityIndicator, Pressable, Text, TextInput, View } from 'react-native'; import { ActivityIndicator, TouchableOpacity, Text, TextInput, View } from 'react-native';
import { Ionicons } from '@expo/vector-icons'; import { Ionicons } from '@expo/vector-icons';
import { useTranslation } from 'react-i18next'; import { useTranslation } from 'react-i18next';
import { useMailConnect } from '../../hooks/useMailConnect'; import { useMailConnect } from '../../hooks/useMailConnect';
@ -64,11 +64,11 @@ export function EditMailAccountSheet({ visible, email, onClose, onSuccess }: Pro
borderBottomColor: colors.border, borderBottomColor: colors.border,
}} }}
> >
<Pressable onPress={handleClose} hitSlop={8}> <TouchableOpacity activeOpacity={0.7} onPress={handleClose} hitSlop={8}>
<Text style={{ fontSize: 15, fontFamily: 'Nunito_400Regular', color: colors.textMuted }}> <Text style={{ fontSize: 15, fontFamily: 'Nunito_400Regular', color: colors.textMuted }}>
{t('mail.edit_account_cancel')} {t('mail.edit_account_cancel')}
</Text> </Text>
</Pressable> </TouchableOpacity>
<Text style={{ fontSize: 16, fontFamily: 'Nunito_700Bold', color: colors.text }}> <Text style={{ fontSize: 16, fontFamily: 'Nunito_700Bold', color: colors.text }}>
{t('mail.edit_account_title')} {t('mail.edit_account_title')}
</Text> </Text>
@ -125,13 +125,13 @@ export function EditMailAccountSheet({ visible, email, onClose, onSuccess }: Pro
color: colors.text, color: colors.text,
}} }}
/> />
<Pressable onPress={() => setPasswordVisible((p) => !p)} hitSlop={8}> <TouchableOpacity activeOpacity={0.7} onPress={() => setPasswordVisible((p) => !p)} hitSlop={8}>
<Ionicons <Ionicons
name={passwordVisible ? 'eye-off-outline' : 'eye-outline'} name={passwordVisible ? 'eye-off-outline' : 'eye-outline'}
size={18} size={18}
color="#737373" color="#737373"
/> />
</Pressable> </TouchableOpacity>
</View> </View>
{(formError ?? connectError) && ( {(formError ?? connectError) && (
@ -161,13 +161,11 @@ export function EditMailAccountSheet({ visible, email, onClose, onSuccess }: Pro
</View> </View>
)} )}
<Pressable <TouchableOpacity
activeOpacity={0.85}
onPress={handleSave} onPress={handleSave}
disabled={!password.trim() || connecting} disabled={!password.trim() || connecting}
style={({ pressed }) => ({ style={{ marginTop: 4 }}
marginTop: 4,
opacity: pressed ? 0.85 : 1,
})}
> >
<View <View
style={{ style={{
@ -185,7 +183,7 @@ export function EditMailAccountSheet({ visible, email, onClose, onSuccess }: Pro
</Text> </Text>
)} )}
</View> </View>
</Pressable> </TouchableOpacity>
</View> </View>
</KeyboardAwareSheet> </KeyboardAwareSheet>
); );

View File

@ -4,6 +4,7 @@ import {
LayoutAnimation, LayoutAnimation,
Platform, Platform,
Pressable, Pressable,
TouchableOpacity,
Text, Text,
UIManager, UIManager,
View, View,
@ -408,8 +409,9 @@ export function MailAccountCard({
const active = account.scanInterval === opt; const active = account.scanInterval === opt;
const disabled = plan === 'free' || updating === account.id; const disabled = plan === 'free' || updating === account.id;
return ( return (
<Pressable <TouchableOpacity
key={opt} key={opt}
activeOpacity={0.7}
disabled={disabled} disabled={disabled}
onPress={() => handleSetInterval(opt)} onPress={() => handleSetInterval(opt)}
style={{ style={{
@ -431,7 +433,7 @@ export function MailAccountCard({
> >
{opt}h {opt}h
</Text> </Text>
</Pressable> </TouchableOpacity>
); );
})} })}
</View> </View>
@ -451,7 +453,8 @@ export function MailAccountCard({
)} )}
<View style={{ flexDirection: 'row' }}> <View style={{ flexDirection: 'row' }}>
<Pressable <TouchableOpacity
activeOpacity={0.7}
onPress={() => setEditVisible(true)} onPress={() => setEditVisible(true)}
style={{ ...ACTION_BTN_BASE, backgroundColor: '#f5f5f5', marginRight: 6 }} style={{ ...ACTION_BTN_BASE, backgroundColor: '#f5f5f5', marginRight: 6 }}
> >
@ -467,8 +470,9 @@ export function MailAccountCard({
> >
{t('mail.account_change_password')} {t('mail.account_change_password')}
</Text> </Text>
</Pressable> </TouchableOpacity>
<Pressable <TouchableOpacity
activeOpacity={0.7}
onPress={() => setConfirmVisible(true)} onPress={() => setConfirmVisible(true)}
disabled={disconnecting} disabled={disconnecting}
style={{ style={{
@ -496,7 +500,7 @@ export function MailAccountCard({
</Text> </Text>
</> </>
)} )}
</Pressable> </TouchableOpacity>
</View> </View>
</View> </View>
)} )}

View File

@ -2,6 +2,7 @@ import {
LayoutAnimation, LayoutAnimation,
Platform, Platform,
Pressable, Pressable,
TouchableOpacity,
Text, Text,
UIManager, UIManager,
View, View,
@ -140,9 +141,9 @@ export function MailActivityLog({ expanded, onToggle }: Props) {
? t('mail.activity_log_more', { count: total - 10 }) ? t('mail.activity_log_more', { count: total - 10 })
: t('mail.activity_log_count', { count: total })} : t('mail.activity_log_count', { count: total })}
</Text> </Text>
<Pressable onPress={refresh} hitSlop={8}> <TouchableOpacity activeOpacity={0.7} onPress={refresh} hitSlop={8}>
<Ionicons name="refresh" size={14} color="#737373" /> <Ionicons name="refresh" size={14} color="#737373" />
</Pressable> </TouchableOpacity>
</View> </View>
</> </>
)} )}

View File

@ -1,5 +1,5 @@
import { useState } from 'react'; import { useState } from 'react';
import { Pressable, Text, View } from 'react-native'; import { TouchableOpacity, Text, View } from 'react-native';
import Svg, { Rect, Text as SvgText } from 'react-native-svg'; import Svg, { Rect, Text as SvgText } from 'react-native-svg';
import { useTranslation } from 'react-i18next'; import { useTranslation } from 'react-i18next';
import { useColors } from '../../lib/theme'; import { useColors } from '../../lib/theme';
@ -145,10 +145,11 @@ export function MailWeeklyChart({ dailyStats, totalBlocked }: Props) {
}} }}
> >
{dailyStats.map((day, i) => ( {dailyStats.map((day, i) => (
<Pressable <TouchableOpacity
key={`tap-${day.date}`} key={`tap-${day.date}`}
style={{ flex: 1, height: '100%' }} style={{ flex: 1, height: '100%' }}
onPress={() => setActiveIdx((prev) => (prev === i ? null : i))} onPress={() => setActiveIdx((prev) => (prev === i ? null : i))}
activeOpacity={0.7}
accessibilityLabel={`${day.label}: ${day.count}`} accessibilityLabel={`${day.label}: ${day.count}`}
/> />
))} ))}

View File

@ -1,5 +1,5 @@
import { useState } from 'react'; import { useState } from 'react';
import { View, Text, Pressable, LayoutAnimation, Platform, UIManager } from 'react-native'; import { View, Text, TouchableOpacity, LayoutAnimation, Platform, UIManager } from 'react-native';
import { Ionicons } from '@expo/vector-icons'; import { Ionicons } from '@expo/vector-icons';
import { useColors } from '../../lib/theme'; import { useColors } from '../../lib/theme';
@ -28,9 +28,9 @@ export function ApprovedDomainsList({ domains, loading }: Props) {
return ( return (
<View style={{ marginHorizontal: 16, marginTop: 12 }}> <View style={{ marginHorizontal: 16, marginTop: 12 }}>
<Pressable <TouchableOpacity
onPress={toggle} onPress={toggle}
style={({ pressed }) => ({ opacity: pressed ? 0.7 : 1 })} activeOpacity={0.7}
> >
<View <View
style={{ style={{
@ -58,7 +58,7 @@ export function ApprovedDomainsList({ domains, loading }: Props) {
color={colors.textMuted} color={colors.textMuted}
/> />
</View> </View>
</Pressable> </TouchableOpacity>
{expanded ? ( {expanded ? (
<View <View

View File

@ -2,7 +2,7 @@ import { useEffect, useRef, useState } from 'react';
import { import {
View, View,
Text, Text,
Pressable, TouchableOpacity,
Switch, Switch,
LayoutAnimation, LayoutAnimation,
Platform, Platform,
@ -215,9 +215,9 @@ export function DemographicsAccordion({
return ( return (
<View style={{ marginHorizontal: 16, marginTop: 24 }}> <View style={{ marginHorizontal: 16, marginTop: 24 }}>
<Pressable <TouchableOpacity
onPress={toggle} onPress={toggle}
style={({ pressed }) => ({ opacity: pressed ? 0.7 : 1 })} activeOpacity={0.7}
> >
<View <View
style={{ style={{
@ -311,7 +311,7 @@ export function DemographicsAccordion({
</View> </View>
)} )}
</View> </View>
</Pressable> </TouchableOpacity>
{expanded ? ( {expanded ? (
<View <View
@ -404,9 +404,9 @@ export function DemographicsAccordion({
} }
shouldOpenOnLongPress={false} shouldOpenOnLongPress={false}
> >
<Pressable <TouchableOpacity
hitSlop={8} hitSlop={8}
style={({ pressed }) => ({ opacity: pressed ? 0.6 : 1 })} activeOpacity={0.6}
> >
<View style={{ flexDirection: 'row', alignItems: 'center', gap: 16 }}> <View style={{ flexDirection: 'row', alignItems: 'center', gap: 16 }}>
<View <View
@ -431,7 +431,7 @@ export function DemographicsAccordion({
</View> </View>
<Ionicons name="chevron-forward" size={16} color={colors.textMuted} /> <Ionicons name="chevron-forward" size={16} color={colors.textMuted} />
</View> </View>
</Pressable> </TouchableOpacity>
</MenuView> </MenuView>
</FieldRow> </FieldRow>
@ -637,9 +637,9 @@ export function DemographicsAccordion({
</FieldRow> </FieldRow>
) : null} ) : null}
<Pressable <TouchableOpacity
onPress={onRevokeConsent} onPress={onRevokeConsent}
style={({ pressed }) => ({ opacity: pressed ? 0.7 : 1 })} activeOpacity={0.7}
> >
<View <View
style={{ style={{
@ -661,7 +661,7 @@ export function DemographicsAccordion({
Einwilligung widerrufen Einwilligung widerrufen
</Text> </Text>
</View> </View>
</Pressable> </TouchableOpacity>
</View> </View>
) : null} ) : null}
@ -753,9 +753,9 @@ function FieldRow({
function SelectButton({ value, onPress }: { value: string | null; onPress: () => void }) { function SelectButton({ value, onPress }: { value: string | null; onPress: () => void }) {
const colors = useColors(); const colors = useColors();
return ( return (
<Pressable <TouchableOpacity
onPress={onPress} onPress={onPress}
style={({ pressed }) => ({ opacity: pressed ? 0.6 : 1 })} activeOpacity={0.6}
> >
<View style={{ flexDirection: 'row', alignItems: 'center', gap: 16 }}> <View style={{ flexDirection: 'row', alignItems: 'center', gap: 16 }}>
{/* Value als Chip */} {/* Value als Chip */}
@ -782,6 +782,6 @@ function SelectButton({ value, onPress }: { value: string | null; onPress: () =>
{/* Chevron-right am Ende, separat vom Chip */} {/* Chevron-right am Ende, separat vom Chip */}
<Ionicons name="chevron-forward" size={16} color={colors.textMuted} /> <Ionicons name="chevron-forward" size={16} color={colors.textMuted} />
</View> </View>
</Pressable> </TouchableOpacity>
); );
} }

View File

@ -1,4 +1,4 @@
import { View, Text, Pressable } from 'react-native'; import { View, Text, TouchableOpacity } from 'react-native';
import { Ionicons } from '@expo/vector-icons'; import { Ionicons } from '@expo/vector-icons';
import { useColors } from '../../lib/theme'; import { useColors } from '../../lib/theme';
@ -60,11 +60,9 @@ export function DigaMissionBanner({ onDismiss, onContribute }: Props) {
</Text> </Text>
<View style={{ flexDirection: 'row', gap: 8, marginTop: 12 }}> <View style={{ flexDirection: 'row', gap: 8, marginTop: 12 }}>
<Pressable <TouchableOpacity
onPress={onContribute} onPress={onContribute}
style={({ pressed }) => ({ activeOpacity={0.7}
opacity: pressed ? 0.7 : 1,
})}
> >
<View style={{ <View style={{
paddingHorizontal: 12, paddingHorizontal: 12,
@ -82,12 +80,10 @@ export function DigaMissionBanner({ onDismiss, onContribute }: Props) {
Beitragen Beitragen
</Text> </Text>
</View> </View>
</Pressable> </TouchableOpacity>
<Pressable <TouchableOpacity
onPress={onDismiss} onPress={onDismiss}
style={({ pressed }) => ({ activeOpacity={0.7}
opacity: pressed ? 0.7 : 1,
})}
> >
<View style={{ <View style={{
paddingHorizontal: 12, paddingHorizontal: 12,
@ -104,16 +100,16 @@ export function DigaMissionBanner({ onDismiss, onContribute }: Props) {
Später Später
</Text> </Text>
</View> </View>
</Pressable> </TouchableOpacity>
</View> </View>
</View> </View>
<Pressable <TouchableOpacity
onPress={onDismiss} onPress={onDismiss}
hitSlop={8} hitSlop={8}
style={({ pressed }) => ({ opacity: pressed ? 0.5 : 1 })} activeOpacity={0.5}
> >
<Ionicons name="close" size={16} color={colors.textMuted} /> <Ionicons name="close" size={16} color={colors.textMuted} />
</Pressable> </TouchableOpacity>
</View> </View>
</View> </View>
); );

View File

@ -1,5 +1,5 @@
import { useState } from 'react'; import { useState } from 'react';
import { View, Text, Pressable, Image } from 'react-native'; import { View, Text, TouchableOpacity, Image } from 'react-native';
import { Ionicons } from '@expo/vector-icons'; import { Ionicons } from '@expo/vector-icons';
import Svg, { Path } from 'react-native-svg'; import Svg, { Path } from 'react-native-svg';
import { useColors } from '../../lib/theme'; import { useColors } from '../../lib/theme';
@ -84,12 +84,10 @@ export function ProfileHeader({
<View style={{ paddingVertical: 24, paddingHorizontal: 20 }}> <View style={{ paddingVertical: 24, paddingHorizontal: 20 }}>
{/* Avatar — Pressable in alignSelf:center-Wrapper (Pressable+style-fn ignoriert alignSelf manchmal in RN) */} {/* Avatar — Pressable in alignSelf:center-Wrapper (Pressable+style-fn ignoriert alignSelf manchmal in RN) */}
<View style={{ alignSelf: 'center' }}> <View style={{ alignSelf: 'center' }}>
<Pressable <TouchableOpacity
onPress={onEditAvatar} onPress={onEditAvatar}
style={({ pressed }) => ({ activeOpacity={0.85}
position: 'relative', style={{ position: 'relative' }}
opacity: pressed ? 0.85 : 1,
})}
> >
<View <View
style={{ style={{
@ -136,21 +134,21 @@ export function ProfileHeader({
> >
<Ionicons name="camera" size={14} color="#ffffff" /> <Ionicons name="camera" size={14} color="#ffffff" />
</View> </View>
</Pressable> </TouchableOpacity>
</View> </View>
{/* Nickname — ganze Zeile Pressable in alignSelf:center-Wrapper */} {/* Nickname — ganze Zeile Pressable in alignSelf:center-Wrapper */}
<View style={{ alignSelf: 'center' }}> <View style={{ alignSelf: 'center' }}>
<Pressable <TouchableOpacity
onPress={onEditNickname} onPress={onEditNickname}
hitSlop={8} hitSlop={8}
style={({ pressed }) => ({ activeOpacity={0.5}
style={{
flexDirection: 'row', flexDirection: 'row',
alignItems: 'center', alignItems: 'center',
marginTop: 16, marginTop: 16,
gap: 6, gap: 6,
opacity: pressed ? 0.5 : 1, }}
})}
> >
<Text <Text
style={{ style={{
@ -161,7 +159,7 @@ export function ProfileHeader({
> >
{nickname} {nickname}
</Text> </Text>
</Pressable> </TouchableOpacity>
</View> </View>
{/* Plan-Tier-Badge direkt unter Nickname — Legend mit sparkles-icon */} {/* Plan-Tier-Badge direkt unter Nickname — Legend mit sparkles-icon */}
@ -249,14 +247,11 @@ export function ProfileHeader({
Parent ist KEIN alignItems:center mehr Hint nimmt natürlich volle Breite (default flex-stretch). Parent ist KEIN alignItems:center mehr Hint nimmt natürlich volle Breite (default flex-stretch).
KEIN width:'100%' (Konflikt mit alignSelf:stretch in alignItems:center-Kontext war der Bug). */} KEIN width:'100%' (Konflikt mit alignSelf:stretch in alignItems:center-Kontext war der Bug). */}
{showDemographicsHint ? ( {showDemographicsHint ? (
<Pressable <TouchableOpacity
onPress={onDemographicsHintPress} onPress={onDemographicsHintPress}
hitSlop={6} hitSlop={6}
style={({ pressed }) => ({ activeOpacity={0.7}
alignSelf: 'center', style={{ alignSelf: 'center', marginTop: 16 }}
marginTop: 16,
opacity: pressed ? 0.7 : 1,
})}
> >
<View <View
style={{ style={{
@ -299,7 +294,7 @@ export function ProfileHeader({
</Text> </Text>
</View> </View>
</View> </View>
</Pressable> </TouchableOpacity>
) : null} ) : null}
</View> </View>
); );

View File

@ -1,4 +1,4 @@
import { View, Text, Pressable } from 'react-native'; import { View, Text, TouchableOpacity } from 'react-native';
import { useColors } from '../../lib/theme'; import { useColors } from '../../lib/theme';
type Props = { type Props = {
@ -19,9 +19,9 @@ type CardProps = {
function StatPill({ value, label, onPress }: CardProps) { function StatPill({ value, label, onPress }: CardProps) {
const colors = useColors(); const colors = useColors();
return ( return (
<Pressable <TouchableOpacity
onPress={onPress} onPress={onPress}
style={({ pressed }) => ({ opacity: pressed ? 0.6 : 1 })} activeOpacity={0.6}
> >
<View <View
style={{ style={{
@ -63,7 +63,7 @@ function StatPill({ value, label, onPress }: CardProps) {
{label} {label}
</Text> </Text>
</View> </View>
</Pressable> </TouchableOpacity>
); );
} }

View File

@ -1,6 +1,6 @@
// 4-7-8 Atemübung: Card (in-chat) + Drawer (bottom sheet). // 4-7-8 Atemübung: Card (in-chat) + Drawer (bottom sheet).
import { useEffect, useRef, useState } from 'react'; import { useEffect, useRef, useState } from 'react';
import { View, Text, Pressable, Animated, StyleSheet } from 'react-native'; import { View, Text, TouchableOpacity, Animated, StyleSheet } from 'react-native';
import { BREATH_PHASES, TOTAL_ROUNDS, type BreathState } from '../../lib/sosConstants'; import { BREATH_PHASES, TOTAL_ROUNDS, type BreathState } from '../../lib/sosConstants';
import { useColors } from '../../lib/theme'; import { useColors } from '../../lib/theme';
@ -87,9 +87,9 @@ export function BreathingCard({ onDone, onSpeak }: Props) {
<View style={{ alignItems: 'center', gap: 16 }}> <View style={{ alignItems: 'center', gap: 16 }}>
<Text style={st.breathTitle}>4-7-8 Atemübung</Text> <Text style={st.breathTitle}>4-7-8 Atemübung</Text>
<Text style={st.breathSub}>3 Runden · beruhigt dein Nervensystem</Text> <Text style={st.breathSub}>3 Runden · beruhigt dein Nervensystem</Text>
<Pressable style={[st.breathStartBtn, { backgroundColor: colors.brandOrange }]} onPress={() => { setCountdown(3); setBreathState('countdown'); }}> <TouchableOpacity activeOpacity={0.7} style={[st.breathStartBtn, { backgroundColor: colors.brandOrange }]} onPress={() => { setCountdown(3); setBreathState('countdown'); }}>
<Text style={st.breathStartTxt}>Starten</Text> <Text style={st.breathStartTxt}>Starten</Text>
</Pressable> </TouchableOpacity>
</View> </View>
) : breathState === 'countdown' ? ( ) : breathState === 'countdown' ? (
<View style={{ alignItems: 'center', gap: 20 }}> <View style={{ alignItems: 'center', gap: 20 }}>

View File

@ -3,6 +3,7 @@ import {
View, View,
Text, Text,
Pressable, Pressable,
TouchableOpacity,
TextInput, TextInput,
StyleSheet, StyleSheet,
Animated, Animated,
@ -80,7 +81,8 @@ export function InlineRatingDrawer({
<Text style={[s.q, { color: colors.textMuted }]}>Fühlst du dich besser?</Text> <Text style={[s.q, { color: colors.textMuted }]}>Fühlst du dich besser?</Text>
<View style={s.btnRow}> <View style={s.btnRow}>
<Pressable <TouchableOpacity
activeOpacity={0.7}
style={[s.choiceBtn, better === true && s.choiceBtnYes]} style={[s.choiceBtn, better === true && s.choiceBtnYes]}
onPress={() => setBetter(true)} onPress={() => setBetter(true)}
> >
@ -90,8 +92,9 @@ export function InlineRatingDrawer({
color={better === true ? '#fff' : '#16a34a'} color={better === true ? '#fff' : '#16a34a'}
/> />
<Text style={[s.choiceTxt, better === true && { color: '#fff' }]}>Ja</Text> <Text style={[s.choiceTxt, better === true && { color: '#fff' }]}>Ja</Text>
</Pressable> </TouchableOpacity>
<Pressable <TouchableOpacity
activeOpacity={0.7}
style={[s.choiceBtn, better === false && s.choiceBtnNo]} style={[s.choiceBtn, better === false && s.choiceBtnNo]}
onPress={() => setBetter(false)} onPress={() => setBetter(false)}
> >
@ -101,19 +104,19 @@ export function InlineRatingDrawer({
color={better === false ? '#fff' : '#dc2626'} color={better === false ? '#fff' : '#dc2626'}
/> />
<Text style={[s.choiceTxt, better === false && { color: '#fff' }]}>Nein</Text> <Text style={[s.choiceTxt, better === false && { color: '#fff' }]}>Nein</Text>
</Pressable> </TouchableOpacity>
</View> </View>
<Text style={[s.q, { color: colors.textMuted }]}>Bewertung</Text> <Text style={[s.q, { color: colors.textMuted }]}>Bewertung</Text>
<View style={s.starsRow}> <View style={s.starsRow}>
{[1, 2, 3, 4, 5].map((n) => ( {[1, 2, 3, 4, 5].map((n) => (
<Pressable key={n} onPress={() => setRating(n)} hitSlop={6}> <TouchableOpacity key={n} onPress={() => setRating(n)} hitSlop={6} activeOpacity={0.7}>
<Ionicons <Ionicons
name={n <= rating ? 'star' : 'star-outline'} name={n <= rating ? 'star' : 'star-outline'}
size={32} size={32}
color={n <= rating ? '#f59e0b' : '#cbd5e1'} color={n <= rating ? '#f59e0b' : '#cbd5e1'}
/> />
</Pressable> </TouchableOpacity>
))} ))}
</View> </View>
@ -129,16 +132,17 @@ export function InlineRatingDrawer({
/> />
<View style={s.actions}> <View style={s.actions}>
<Pressable style={s.cancelBtn} onPress={onClose}> <TouchableOpacity activeOpacity={0.7} style={s.cancelBtn} onPress={onClose}>
<Text style={s.cancelTxt}>Abbrechen</Text> <Text style={s.cancelTxt}>Abbrechen</Text>
</Pressable> </TouchableOpacity>
<Pressable <TouchableOpacity
activeOpacity={0.7}
style={[s.submitBtn, { backgroundColor: colors.brandOrange }, submitting && { opacity: 0.6 }]} style={[s.submitBtn, { backgroundColor: colors.brandOrange }, submitting && { opacity: 0.6 }]}
onPress={submit} onPress={submit}
disabled={submitting} disabled={submitting}
> >
<Text style={s.submitTxt}>{submitting ? 'Sende…' : 'Senden'}</Text> <Text style={s.submitTxt}>{submitting ? 'Sende…' : 'Senden'}</Text>
</Pressable> </TouchableOpacity>
</View> </View>
</ScrollView> </ScrollView>
</KeyboardAvoidingView> </KeyboardAvoidingView>

View File

@ -1,4 +1,4 @@
import { Pressable, Text, View } from 'react-native'; import { TouchableOpacity, Text, View } from 'react-native';
import { LLM_PROVIDER_LABEL, type LlmProvider, useLlmProvider } from '../../lib/llmProvider'; import { LLM_PROVIDER_LABEL, type LlmProvider, useLlmProvider } from '../../lib/llmProvider';
const PROVIDERS: LlmProvider[] = ['auto', 'openrouter-sonnet', 'openrouter-haiku', 'groq-llama']; const PROVIDERS: LlmProvider[] = ['auto', 'openrouter-sonnet', 'openrouter-haiku', 'groq-llama'];
@ -30,10 +30,11 @@ export function LlmProviderToggle() {
{PROVIDERS.map((p) => { {PROVIDERS.map((p) => {
const active = p === current; const active = p === current;
return ( return (
<Pressable <TouchableOpacity
key={p} key={p}
onPress={() => { void set(p); }} onPress={() => { void set(p); }}
hitSlop={6} hitSlop={6}
activeOpacity={0.7}
style={{ style={{
paddingHorizontal: 10, paddingHorizontal: 10,
paddingVertical: 4, paddingVertical: 4,
@ -52,7 +53,7 @@ export function LlmProviderToggle() {
> >
{LLM_PROVIDER_LABEL[p]} {LLM_PROVIDER_LABEL[p]}
</Text> </Text>
</Pressable> </TouchableOpacity>
); );
})} })}
</View> </View>

View File

@ -1,6 +1,6 @@
// Chat-Bubble + Spezial-Cards (Spiele/Überwunden) für den SOS-Chat-Stream // Chat-Bubble + Spezial-Cards (Spiele/Überwunden) für den SOS-Chat-Stream
// sowie GameHeader für die aktive Spiel-Session. // sowie GameHeader für die aktive Spiel-Session.
import { View, Text, Pressable, StyleSheet } from 'react-native'; import { View, Text, TouchableOpacity, StyleSheet } from 'react-native';
import { Ionicons } from '@expo/vector-icons'; import { Ionicons } from '@expo/vector-icons';
import { type GameType, GAME_META, GamePickerGrid } from './UrgeGames'; import { type GameType, GAME_META, GamePickerGrid } from './UrgeGames';
import { RiveAvatar, type Emotion as LyraEmotion } from '../RiveAvatar'; import { RiveAvatar, type Emotion as LyraEmotion } from '../RiveAvatar';
@ -57,7 +57,7 @@ export function GameHeader({ game, emotion, onBack }: { game: GameType; emotion:
const meta = GAME_META.find((g) => g.id === game); const meta = GAME_META.find((g) => g.id === game);
return ( return (
<View style={st.gameHeader}> <View style={st.gameHeader}>
<Pressable style={st.backBtn} onPress={onBack} hitSlop={12}><Ionicons name="chevron-back" size={22} color="#374151" /></Pressable> <TouchableOpacity activeOpacity={0.7} style={st.backBtn} onPress={onBack} hitSlop={12}><Ionicons name="chevron-back" size={22} color="#374151" /></TouchableOpacity>
<View style={{ alignItems: 'center', flex: 1 }}> <View style={{ alignItems: 'center', flex: 1 }}>
<View style={{ transform: [{ scale: 0.65 }], marginBottom: -8 }}><RiveAvatar emotion={emotion} size="sm" /></View> <View style={{ transform: [{ scale: 0.65 }], marginBottom: -8 }}><RiveAvatar emotion={emotion} size="sm" /></View>
<Text style={{ fontFamily: 'Nunito_700Bold', fontSize: 13, color: '#111827', marginTop: 2 }}>{meta?.id ?? game}</Text> <Text style={{ fontFamily: 'Nunito_700Bold', fontSize: 13, color: '#111827', marginTop: 2 }}>{meta?.id ?? game}</Text>

View File

@ -3,6 +3,7 @@ import {
View, View,
Text, Text,
Pressable, Pressable,
TouchableOpacity,
TextInput, TextInput,
StyleSheet, StyleSheet,
Animated, Animated,
@ -106,19 +107,21 @@ export function ShareSuccessDrawer({
<View style={s.row}> <View style={s.row}>
{onRegenerate && ( {onRegenerate && (
<Pressable <TouchableOpacity
activeOpacity={0.7}
style={s.secondaryBtn} style={s.secondaryBtn}
onPress={onRegenerate} onPress={onRegenerate}
disabled={generating} disabled={generating}
> >
<Ionicons name="refresh" size={16} color="#475569" /> <Ionicons name="refresh" size={16} color="#475569" />
<Text style={s.secondaryTxt}>Neu generieren</Text> <Text style={s.secondaryTxt}>Neu generieren</Text>
</Pressable> </TouchableOpacity>
)} )}
<Pressable style={s.cancelBtn} onPress={onClose}> <TouchableOpacity activeOpacity={0.7} style={s.cancelBtn} onPress={onClose}>
<Text style={s.cancelTxt}>Abbrechen</Text> <Text style={s.cancelTxt}>Abbrechen</Text>
</Pressable> </TouchableOpacity>
<Pressable <TouchableOpacity
activeOpacity={0.7}
style={[s.shareBtn, { backgroundColor: colors.brandOrange }, (!text.trim() || submitting || generating) && s.shareBtnDisabled]} style={[s.shareBtn, { backgroundColor: colors.brandOrange }, (!text.trim() || submitting || generating) && s.shareBtnDisabled]}
onPress={handleShare} onPress={handleShare}
disabled={!text.trim() || submitting || generating} disabled={!text.trim() || submitting || generating}
@ -131,7 +134,7 @@ export function ShareSuccessDrawer({
<Text style={s.shareTxt}>Teilen</Text> <Text style={s.shareTxt}>Teilen</Text>
</> </>
)} )}
</Pressable> </TouchableOpacity>
</View> </View>
</ScrollView> </ScrollView>
</KeyboardAvoidingView> </KeyboardAvoidingView>

View File

@ -1,5 +1,5 @@
import { useState } from 'react'; import { useState } from 'react';
import { View, Text, Pressable, TextInput, Modal, StyleSheet, Platform, KeyboardAvoidingView, ScrollView } from 'react-native'; import { View, Text, Pressable, TouchableOpacity, TextInput, Modal, StyleSheet, Platform, KeyboardAvoidingView, ScrollView } from 'react-native';
import { Ionicons } from '@expo/vector-icons'; import { Ionicons } from '@expo/vector-icons';
import { useColors } from '../../lib/theme'; import { useColors } from '../../lib/theme';
@ -51,33 +51,35 @@ export function SosFeedbackModal({
{/* Better Yes/No */} {/* Better Yes/No */}
<Text style={[s.q, { color: colors.textMuted }]}>Fühlst du dich besser?</Text> <Text style={[s.q, { color: colors.textMuted }]}>Fühlst du dich besser?</Text>
<View style={s.btnRow}> <View style={s.btnRow}>
<Pressable <TouchableOpacity
activeOpacity={0.7}
style={[s.choiceBtn, better === true && s.choiceBtnYes]} style={[s.choiceBtn, better === true && s.choiceBtnYes]}
onPress={() => setBetter(true)} onPress={() => setBetter(true)}
> >
<Ionicons name="checkmark-circle" size={18} color={better === true ? '#fff' : '#16a34a'} /> <Ionicons name="checkmark-circle" size={18} color={better === true ? '#fff' : '#16a34a'} />
<Text style={[s.choiceTxt, better === true && { color: '#fff' }]}>Ja</Text> <Text style={[s.choiceTxt, better === true && { color: '#fff' }]}>Ja</Text>
</Pressable> </TouchableOpacity>
<Pressable <TouchableOpacity
activeOpacity={0.7}
style={[s.choiceBtn, better === false && s.choiceBtnNo]} style={[s.choiceBtn, better === false && s.choiceBtnNo]}
onPress={() => setBetter(false)} onPress={() => setBetter(false)}
> >
<Ionicons name="close-circle" size={18} color={better === false ? '#fff' : '#dc2626'} /> <Ionicons name="close-circle" size={18} color={better === false ? '#fff' : '#dc2626'} />
<Text style={[s.choiceTxt, better === false && { color: '#fff' }]}>Nein</Text> <Text style={[s.choiceTxt, better === false && { color: '#fff' }]}>Nein</Text>
</Pressable> </TouchableOpacity>
</View> </View>
{/* Stars */} {/* Stars */}
<Text style={[s.q, { color: colors.textMuted }]}>Bewertung</Text> <Text style={[s.q, { color: colors.textMuted }]}>Bewertung</Text>
<View style={s.starsRow}> <View style={s.starsRow}>
{[1, 2, 3, 4, 5].map((n) => ( {[1, 2, 3, 4, 5].map((n) => (
<Pressable key={n} onPress={() => setRating(n)} hitSlop={6}> <TouchableOpacity key={n} onPress={() => setRating(n)} hitSlop={6} activeOpacity={0.7}>
<Ionicons <Ionicons
name={n <= rating ? 'star' : 'star-outline'} name={n <= rating ? 'star' : 'star-outline'}
size={32} size={32}
color={n <= rating ? '#f59e0b' : '#cbd5e1'} color={n <= rating ? '#f59e0b' : '#cbd5e1'}
/> />
</Pressable> </TouchableOpacity>
))} ))}
</View> </View>
@ -96,12 +98,12 @@ export function SosFeedbackModal({
{/* Actions */} {/* Actions */}
<View style={s.actions}> <View style={s.actions}>
<Pressable style={s.skipBtn} onPress={skip}> <TouchableOpacity activeOpacity={0.7} style={s.skipBtn} onPress={skip}>
<Text style={s.skipTxt}>Überspringen</Text> <Text style={s.skipTxt}>Überspringen</Text>
</Pressable> </TouchableOpacity>
<Pressable style={[s.submitBtn, { backgroundColor: colors.brandOrange }]} onPress={submit}> <TouchableOpacity activeOpacity={0.7} style={[s.submitBtn, { backgroundColor: colors.brandOrange }]} onPress={submit}>
<Text style={s.submitTxt}>Senden</Text> <Text style={s.submitTxt}>Senden</Text>
</Pressable> </TouchableOpacity>
</View> </View>
</View> </View>
</ScrollView> </ScrollView>

View File

@ -1,4 +1,4 @@
import { Pressable, Text, View } from 'react-native'; import { TouchableOpacity, Text, View } from 'react-native';
import { TTS_PROVIDER_LABEL, type TtsProvider, useTtsProvider } from '../../lib/ttsProvider'; import { TTS_PROVIDER_LABEL, type TtsProvider, useTtsProvider } from '../../lib/ttsProvider';
const PROVIDERS: TtsProvider[] = ['openai', 'gemini', 'elevenlabs', 'cartesia', 'google-cloud']; const PROVIDERS: TtsProvider[] = ['openai', 'gemini', 'elevenlabs', 'cartesia', 'google-cloud'];
@ -30,10 +30,11 @@ export function TtsProviderToggle() {
{PROVIDERS.map((p) => { {PROVIDERS.map((p) => {
const active = p === current; const active = p === current;
return ( return (
<Pressable <TouchableOpacity
key={p} key={p}
onPress={() => { void set(p); }} onPress={() => { void set(p); }}
hitSlop={6} hitSlop={6}
activeOpacity={0.7}
style={{ style={{
paddingHorizontal: 10, paddingHorizontal: 10,
paddingVertical: 4, paddingVertical: 4,
@ -52,7 +53,7 @@ export function TtsProviderToggle() {
> >
{TTS_PROVIDER_LABEL[p]} {TTS_PROVIDER_LABEL[p]}
</Text> </Text>
</Pressable> </TouchableOpacity>
); );
})} })}
</View> </View>

View File

@ -1,5 +1,5 @@
import { useEffect, useMemo, useRef, useState, useCallback } from 'react'; import { useEffect, useMemo, useRef, useState, useCallback } from 'react';
import { View, Text, Pressable, TouchableWithoutFeedback, Dimensions, PanResponder, Platform } from 'react-native'; import { View, Text, Pressable, TouchableOpacity, TouchableWithoutFeedback, Dimensions, PanResponder, Platform } from 'react-native';
import Svg, { Defs, Pattern, Path, Rect, Polyline, Circle, Line } from 'react-native-svg'; import Svg, { Defs, Pattern, Path, Rect, Polyline, Circle, Line } from 'react-native-svg';
import { SvgXml } from 'react-native-svg'; import { SvgXml } from 'react-native-svg';
import { Ionicons } from '@expo/vector-icons'; import { Ionicons } from '@expo/vector-icons';
@ -38,13 +38,11 @@ export function GamePickerGrid({ onSelect }: { onSelect: (game: GameType) => voi
return ( return (
<View style={{ flexDirection: 'row', flexWrap: 'wrap', gap: 10 }}> <View style={{ flexDirection: 'row', flexWrap: 'wrap', gap: 10 }}>
{GAME_META.map((game) => ( {GAME_META.map((game) => (
<Pressable <TouchableOpacity
key={game.id} key={game.id}
onPress={() => onSelect(game.id)} onPress={() => onSelect(game.id)}
style={({ pressed }) => ({ activeOpacity={0.75}
width: '47%', style={{ width: '47%' }}
opacity: pressed ? 0.75 : 1,
})}
> >
<View style={{ <View style={{
aspectRatio: 1, aspectRatio: 1,
@ -75,7 +73,7 @@ export function GamePickerGrid({ onSelect }: { onSelect: (game: GameType) => voi
{t(game.descKey)} {t(game.descKey)}
</Text> </Text>
</View> </View>
</Pressable> </TouchableOpacity>
))} ))}
</View> </View>
); );