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

View File

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

View File

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

View File

@ -3,7 +3,7 @@ import {
View,
Text,
TextInput,
Pressable,
TouchableOpacity,
KeyboardAvoidingView,
Platform,
ActivityIndicator,
@ -168,10 +168,11 @@ export default function ConfirmOtpScreen() {
</View>
)}
<Pressable
<TouchableOpacity
onPress={verify}
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 }}
>
{loading ? (
@ -179,11 +180,12 @@ export default function ConfirmOtpScreen() {
) : (
<Text className="text-white text-base" style={{ fontFamily: 'Nunito_600SemiBold' }}>{t('auth.confirmBtn')}</Text>
)}
</Pressable>
</TouchableOpacity>
<Pressable
<TouchableOpacity
onPress={resend}
disabled={resendCooldown > 0 || loading}
activeOpacity={0.7}
className="py-3 items-center"
>
<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')}
</Text>
</Text>
</Pressable>
</TouchableOpacity>
<Pressable
<TouchableOpacity
onPress={() => router.back()}
activeOpacity={0.7}
className="py-3 items-center mt-2"
>
<Text className="text-neutral-400 text-sm" style={{ fontFamily: 'Nunito_400Regular' }}>{t('auth.backToSignup')}</Text>
</Pressable>
</TouchableOpacity>
</View>
</KeyboardAvoidingView>
</SafeAreaView>

View File

@ -1,5 +1,5 @@
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 { SafeAreaView } from 'react-native-safe-area-context';
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>
<Pressable
<TouchableOpacity
onPress={() => router.replace('/signin')}
activeOpacity={0.7}
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>
</Pressable>
</TouchableOpacity>
</>
)}
</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 { SafeAreaView } from 'react-native-safe-area-context';
import { useTranslation } from 'react-i18next';
@ -25,19 +25,21 @@ export default function DeviceLimitScreen() {
{/* TODO Phase 4: device management — list active devices + revoke button */}
<Pressable
<TouchableOpacity
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>
</Pressable>
</TouchableOpacity>
<Pressable
<TouchableOpacity
onPress={() => router.push('/signin')}
activeOpacity={0.7}
className="py-3 mt-2"
>
<Text className="text-neutral-500 text-sm" style={{ fontFamily: 'Nunito_400Regular' }}>{t('auth.deviceLimitUpgrade')}</Text>
</Pressable>
</TouchableOpacity>
</View>
</SafeAreaView>
);

View File

@ -3,7 +3,7 @@ import {
View,
Text,
TextInput,
Pressable,
TouchableOpacity,
KeyboardAvoidingView,
Platform,
ActivityIndicator,
@ -78,10 +78,11 @@ export default function ForgotPasswordScreen() {
</View>
)}
<Pressable
<TouchableOpacity
onPress={onSubmit}
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 }}
>
{loading ? (
@ -89,7 +90,7 @@ export default function ForgotPasswordScreen() {
) : (
<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">
@ -100,12 +101,13 @@ export default function ForgotPasswordScreen() {
</View>
)}
<Pressable
<TouchableOpacity
onPress={() => router.back()}
activeOpacity={0.7}
className="py-4 items-center mt-2"
>
<Text className="text-neutral-500 text-sm" style={{ fontFamily: 'Nunito_400Regular' }}>{t('auth.backToLogin')}</Text>
</Pressable>
</TouchableOpacity>
</View>
</KeyboardAvoidingView>
</SafeAreaView>

View File

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

View File

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

View File

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

View File

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

View File

@ -3,7 +3,7 @@ import {
View,
Text,
FlatList,
Pressable,
TouchableOpacity,
KeyboardAvoidingView,
Platform,
ActivityIndicator,
@ -236,9 +236,9 @@ export default function DmScreen() {
<SafeAreaView style={styles.container} edges={['top']}>
{/* 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} />
</Pressable>
</TouchableOpacity>
<View style={styles.headerCenter}>
<View style={styles.headerAvatar}>
{partner?.avatar ? (

View File

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

View File

@ -9,7 +9,7 @@ import {
Text,
TextInput,
FlatList,
Pressable,
TouchableOpacity,
Platform,
Animated,
Keyboard,
@ -550,9 +550,9 @@ export default function CoachScreen() {
{/* Floating header — no bar, avatar + 2 icon buttons hover over chat */}
<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} />
</Pressable>
</TouchableOpacity>
<View style={styles.avatarCenter}>
<View pointerEvents="none">
@ -587,17 +587,17 @@ export default function CoachScreen() {
<View style={styles.speakingRow}>
<VoiceBars count={5} baseColor={colors.brandOrange} />
<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} />
</Pressable>
</TouchableOpacity>
</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} />
</Pressable>
</TouchableOpacity>
</View>
{/* Content area */}
@ -637,9 +637,9 @@ export default function CoachScreen() {
{/* Scroll-to-bottom button */}
{showScrollBtn && (
<Pressable style={styles.scrollDownBtn} onPress={scrollToBottom}>
<TouchableOpacity style={styles.scrollDownBtn} onPress={scrollToBottom} activeOpacity={0.8}>
<Ionicons name="chevron-down" size={18} color="#fff" />
</Pressable>
</TouchableOpacity>
)}
</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 }]}>
{isRecording ? (
<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" />
</Pressable>
</TouchableOpacity>
<View style={styles.pulseDot} />
<Text style={styles.recordingTimer}>{formatDuration(recordingDuration)}</Text>
<View style={{ flex: 1 }}>
@ -677,28 +677,30 @@ export default function CoachScreen() {
)}
{!isTranscribing && (
<Pressable
<TouchableOpacity
style={[styles.micBtn, { backgroundColor: colors.surfaceElevated }, isRecording && styles.micBtnActive, thinking && styles.micBtnDisabled]}
onPressIn={onMicDown}
onPressOut={onMicUp}
disabled={thinking}
activeOpacity={0.7}
>
<Ionicons
name={isRecording ? 'square' : 'mic'}
size={18}
color={isRecording ? '#fff' : colors.textMuted}
/>
</Pressable>
</TouchableOpacity>
)}
{!isRecording && !isTranscribing && input.trim() !== '' && (
<Pressable
<TouchableOpacity
style={[styles.sendBtn, thinking && styles.sendBtnDisabled]}
onPress={handleSend}
disabled={thinking || !input.trim()}
activeOpacity={0.7}
>
<Ionicons name="send" size={16} color="#fff" />
</Pressable>
</TouchableOpacity>
)}
</View>
</KeyboardAvoidingView>

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@ -1,5 +1,5 @@
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 —
// User sieht zwei Modals nacheinander (Confirm → Success), beide animierte Icons
// = visuelle Doppel-Eskalation, wirkt verwirrend. Daher: Card animiert auf,
@ -74,7 +74,8 @@ export function ConfirmAlert({
return (
<Modal visible={visible} transparent animationType="fade" onRequestClose={onCancel}>
<Pressable
<TouchableOpacity
activeOpacity={1}
onPress={onCancel}
style={{
flex: 1,
@ -84,7 +85,7 @@ export function ConfirmAlert({
padding: 24,
}}
>
<Pressable onPress={() => {}} style={{ width: '85%', maxWidth: 340 }}>
<TouchableOpacity activeOpacity={1} onPress={() => {}} style={{ width: '85%', maxWidth: 340 }}>
<Animated.View
style={{
backgroundColor: '#fff',
@ -150,7 +151,8 @@ export function ConfirmAlert({
{/* Two buttons row */}
<View style={{ flexDirection: 'row' }}>
<View style={{ flex: 1, marginRight: 5 }}>
<Pressable
<TouchableOpacity
activeOpacity={0.7}
onPress={onCancel}
style={{
paddingVertical: 10,
@ -169,10 +171,11 @@ export function ConfirmAlert({
>
{resolvedCancelLabel}
</Text>
</Pressable>
</TouchableOpacity>
</View>
<View style={{ flex: 1, marginLeft: 5 }}>
<Pressable
<TouchableOpacity
activeOpacity={0.7}
onPress={onConfirm}
style={{
paddingVertical: 10,
@ -193,12 +196,12 @@ export function ConfirmAlert({
>
{resolvedConfirmLabel}
</Text>
</Pressable>
</TouchableOpacity>
</View>
</View>
</Animated.View>
</Pressable>
</Pressable>
</TouchableOpacity>
</TouchableOpacity>
</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 { TrueSheet, type SheetDetent } from '@lodev09/react-native-true-sheet';
import { Ionicons } from '@expo/vector-icons';
@ -106,13 +106,13 @@ function DeviceLimitRow({
{removing ? (
<ActivityIndicator size="small" color={colors.error} />
) : (
<Pressable
<TouchableOpacity
onPress={() => onRemove(device.id)}
hitSlop={8}
style={({ pressed }) => ({ opacity: pressed ? 0.5 : 1 })}
activeOpacity={0.5}
>
<Ionicons name="trash-outline" size={18} color={colors.error} />
</Pressable>
</TouchableOpacity>
)}
</View>
);

View File

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

View File

@ -1,5 +1,5 @@
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 { useRouter, type RelativePathString } from 'expo-router';
import { useTranslation } from 'react-i18next';
@ -239,11 +239,9 @@ function NotificationRow({
const avatarUrl = isSocial ? resolveAvatar(notif.actorAvatar, notif.actorName) : null;
return (
<Pressable
<TouchableOpacity
onPress={onPress}
style={({ pressed }) => ({
opacity: pressed ? 0.65 : 1,
})}
activeOpacity={0.65}
>
<View
style={{
@ -319,6 +317,6 @@ function NotificationRow({
</Text>
</View>
</View>
</Pressable>
</TouchableOpacity>
);
}

View File

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

View File

@ -275,7 +275,6 @@ function PostCardImpl({ post, onCommentPress }: Props) {
hitSlop={{ top: 12, bottom: 12, left: 12, right: 12 }}
android_ripple={{ color: 'rgba(220,38,38,0.12)', borderless: true, radius: 22 }}
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 }] }}>
<Ionicons
@ -294,7 +293,6 @@ function PostCardImpl({ post, onCommentPress }: Props) {
hitSlop={{ top: 12, bottom: 12, left: 12, right: 12 }}
android_ripple={{ color: 'rgba(0,0,0,0.08)', borderless: true, radius: 22 }}
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" />
{post.commentsCount > 0 && (
@ -514,7 +512,7 @@ function DomainVoteCard({
onPress={() => onVote('yes')}
disabled={voting}
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" />
<Text className="text-sm text-rebreak-500" style={{ fontFamily: 'Nunito_600SemiBold' }}>
@ -525,7 +523,7 @@ function DomainVoteCard({
onPress={() => onVote('no')}
disabled={voting}
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" />
<Text className="text-sm text-neutral-500" style={{ fontFamily: 'Nunito_600SemiBold' }}>

View File

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

View File

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

View File

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

View File

@ -4,6 +4,7 @@ import {
Text,
TextInput,
Pressable,
TouchableOpacity,
Image,
ActivityIndicator,
} from 'react-native';
@ -262,13 +263,11 @@ export function AddDomainSheet({ visible, tier, onClose, onAdd }: Props) {
<View style={{ flex: 1 }} />
{/* Add-Button */}
<Pressable
<TouchableOpacity
onPress={handleAdd}
disabled={!valid || !confirmPermanent || adding}
style={({ pressed }) => ({
opacity: pressed ? 0.85 : 1,
marginBottom: insets.bottom > 0 ? 8 : 12,
})}
activeOpacity={0.85}
style={{ marginBottom: insets.bottom > 0 ? 8 : 12 }}
>
<View
style={{
@ -286,7 +285,7 @@ export function AddDomainSheet({ visible, tier, onClose, onAdd }: Props) {
</Text>
)}
</View>
</Pressable>
</TouchableOpacity>
</View>
</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 { useState } from 'react';
import { useTranslation } from 'react-i18next';
@ -52,13 +52,12 @@ export function CooldownBanner({ remainingFormatted, onCancel }: Props) {
{remainingFormatted}
</Text>
</View>
<Pressable
<TouchableOpacity
onPress={handleCancel}
disabled={cancelling}
hitSlop={8}
style={({ pressed }) => ({
opacity: pressed || cancelling ? 0.7 : 1,
})}
activeOpacity={0.7}
style={{ opacity: cancelling ? 0.7 : 1 }}
>
<View style={{
paddingHorizontal: 12,
@ -74,7 +73,7 @@ export function CooldownBanner({ remainingFormatted, onCancel }: Props) {
</Text>
)}
</View>
</Pressable>
</TouchableOpacity>
</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 { useState } from 'react';
import { useTranslation } from 'react-i18next';
@ -138,11 +138,9 @@ export function DeactivationExplainerSheet({
<View style={{ height: 12 }} />
{/* Primary Deflector */}
<Pressable
<TouchableOpacity
onPress={onBreathe}
style={({ pressed }) => ({
opacity: pressed ? 0.85 : 1,
})}
activeOpacity={0.85}
>
<View style={{
backgroundColor: '#16a34a',
@ -159,18 +157,19 @@ export function DeactivationExplainerSheet({
{t('blocker.deactivation_breathe_cta')}
</Text>
</View>
</Pressable>
</TouchableOpacity>
{/* Destructive secondary */}
<Pressable
<TouchableOpacity
onPress={showFinalConfirm}
disabled={submitting}
hitSlop={8}
style={({ pressed }) => ({
opacity: pressed || submitting ? 0.5 : 1,
activeOpacity={0.5}
style={{
opacity: submitting ? 0.5 : 1,
alignSelf: 'center',
paddingVertical: 12,
})}
}}
>
<Text
style={{
@ -181,7 +180,7 @@ export function DeactivationExplainerSheet({
>
{submitting ? t('blocker.deactivation_starting') : t('blocker.deactivation_start_anyway')}
</Text>
</Pressable>
</TouchableOpacity>
</ScrollView>
</View>
</Modal>

View File

@ -3,6 +3,7 @@ import {
View,
Text,
Pressable,
TouchableOpacity,
Image,
ActivityIndicator,
} from 'react-native';
@ -138,11 +139,9 @@ export function DomainGrid({ domains, tier, onAdd, onSubmit, onUpgradePro }: Pro
{/* Limit-Reached Upsell (nur Free) */}
{tier.atLimit && tier.plan === 'free' && (
<Pressable
<TouchableOpacity
onPress={onUpgradePro}
style={({ pressed }) => ({
opacity: pressed ? 0.85 : 1,
})}
activeOpacity={0.85}
>
<View style={{
backgroundColor: '#eff6ff',
@ -164,7 +163,7 @@ export function DomainGrid({ domains, tier, onAdd, onSubmit, onUpgradePro }: Pro
</Text>
</View>
</View>
</Pressable>
</TouchableOpacity>
)}
{/* 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 { useTranslation } from 'react-i18next';
import type { ProtectionState } from '../../lib/protection';
@ -89,12 +89,10 @@ export function ProtectionCard({ state, loading, onActivate, onPressSettings }:
{loading ? (
<ActivityIndicator color={iconColor} />
) : isActive ? (
<Pressable
<TouchableOpacity
onPress={onPressSettings}
hitSlop={10}
style={({ pressed }) => ({
opacity: pressed ? 0.6 : 1,
})}
activeOpacity={0.6}
accessibilityLabel={t('blocker.protection_settings_a11y')}
>
<View style={{
@ -111,7 +109,7 @@ export function ProtectionCard({ state, loading, onActivate, onPressSettings }:
}}>
<Ionicons name="settings-outline" size={18} color={colors.textMuted} />
</View>
</Pressable>
</TouchableOpacity>
) : (
<Switch
value={false}

View File

@ -4,6 +4,7 @@ import {
View,
Text,
Pressable,
TouchableOpacity,
ScrollView,
Dimensions,
Animated,
@ -343,13 +344,11 @@ export function ProtectionDetailsSheet({
))}
</View>
{/* MEHR INFO outline button: Pressable=card, inner View=flex-row */}
<Pressable
{/* MEHR INFO outline button: TouchableOpacity=card, inner View=flex-row */}
<TouchableOpacity
onPress={onRequestDeactivation}
style={({ pressed }) => ({
marginTop: 4,
opacity: pressed ? 0.75 : 1,
})}
activeOpacity={0.75}
style={{ marginTop: 4 }}
>
<View style={{
paddingVertical: 14,
@ -368,7 +367,7 @@ export function ProtectionDetailsSheet({
{t('blocker.more_info_title')}
</Text>
</View>
</Pressable>
</TouchableOpacity>
</ScrollView>
</Animated.View>
</Animated.View>
@ -676,11 +675,9 @@ function FaqItem({ question, answer }: { question: string; answer: string }) {
backgroundColor: colors.bg,
}}
>
<Pressable
<TouchableOpacity
onPress={() => setOpen((v) => !v)}
style={({ pressed }) => ({
opacity: pressed ? 0.75 : 1,
})}
activeOpacity={0.75}
>
<View style={{ flexDirection: 'row', alignItems: 'center', paddingHorizontal: 14, paddingVertical: 14 }}>
<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} />
</Animated.View>
</View>
</Pressable>
</TouchableOpacity>
{open && (
<View style={{ paddingHorizontal: 14, paddingBottom: 14, paddingTop: 0 }}>
<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 { useTranslation } from 'react-i18next';
import type { ProtectionState } from '../../lib/protection';
@ -74,12 +74,10 @@ export function ProtectionLockedCard({ state, onPressSettings }: Props) {
</Text>
</View>
<Pressable
<TouchableOpacity
onPress={onPressSettings}
hitSlop={10}
style={({ pressed }) => ({
opacity: pressed ? 0.6 : 1,
})}
activeOpacity={0.6}
accessibilityLabel={t('blocker.protection_settings_a11y')}
>
<View style={{
@ -96,7 +94,7 @@ export function ProtectionLockedCard({ state, onPressSettings }: Props) {
}}>
<Ionicons name="settings-outline" size={18} color={colors.textMuted} />
</View>
</Pressable>
</TouchableOpacity>
</View>
{/* Stats nur wenn aktiv und kein Cooldown */}

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@ -1,6 +1,6 @@
// 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.
import { View, Text, Pressable } from 'react-native';
import { View, Text, TouchableOpacity } from 'react-native';
import { SvgXml } from 'react-native-svg';
import { useTranslation } from 'react-i18next';
import { GameRatingStars } from './GameRatingStars';
@ -27,13 +27,10 @@ export function GameCard({
}: GameCardProps) {
const { t } = useTranslation();
return (
<Pressable
<TouchableOpacity
onPress={() => onPress(id)}
style={({ pressed }) => ({
width: '100%',
transform: [{ scale: pressed ? 0.97 : 1 }],
opacity: pressed ? 0.85 : 1,
})}
activeOpacity={0.85}
style={{ width: '100%' }}
>
<View style={{
borderRadius: 18,
@ -76,6 +73,6 @@ export function GameCard({
</View>
</View>
</View>
</Pressable>
</TouchableOpacity>
);
}

View File

@ -1,6 +1,6 @@
// RN-Port von apps/rebreak/app/components/StarRating.vue
// 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 { useState } from 'react';
@ -83,15 +83,14 @@ export function StarRating({
if (interactive) {
stars.push(
<Pressable
<TouchableOpacity
key={`press-${i}`}
onPress={() => onChange?.(i)}
onHoverIn={() => setHover(i)}
onHoverOut={() => setHover(0)}
hitSlop={4}
activeOpacity={0.7}
>
{star}
</Pressable>
</TouchableOpacity>
);
} else {
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 { Ionicons } from '@expo/vector-icons';
import { useTranslation } from 'react-i18next';
@ -166,14 +166,13 @@ export function HeaderDropdownMenu({ visible, onClose, topOffset = 80 }: Props)
{/* Profile · Settings · Games · [Debug DEV] */}
{items.map((item) => (
<Pressable
<TouchableOpacity
key={item.key}
onPress={() => {
onClose();
void item.onSelect();
}}
android_ripple={{ color: colors.surfaceElevated }}
style={({ pressed }) => ({ opacity: pressed ? 0.7 : 1 })}
activeOpacity={0.7}
>
<View
style={{
@ -199,16 +198,15 @@ export function HeaderDropdownMenu({ visible, onClose, topOffset = 80 }: Props)
{item.label}
</Text>
</View>
</Pressable>
</TouchableOpacity>
))}
<View style={{ height: 1, backgroundColor: colors.border }} />
{/* Abmelden — neutral, nicht rot */}
<Pressable
<TouchableOpacity
onPress={handleLogout}
android_ripple={{ color: colors.surfaceElevated }}
style={({ pressed }) => ({ opacity: pressed ? 0.7 : 1 })}
activeOpacity={0.7}
>
<View
style={{
@ -234,7 +232,7 @@ export function HeaderDropdownMenu({ visible, onClose, topOffset = 80 }: Props)
{t('headerMenu.logout')}
</Text>
</View>
</Pressable>
</TouchableOpacity>
</View>
</Pressable>
</Modal>

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@ -1,5 +1,5 @@
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 { useColors } from '../../lib/theme';
@ -28,9 +28,9 @@ export function ApprovedDomainsList({ domains, loading }: Props) {
return (
<View style={{ marginHorizontal: 16, marginTop: 12 }}>
<Pressable
<TouchableOpacity
onPress={toggle}
style={({ pressed }) => ({ opacity: pressed ? 0.7 : 1 })}
activeOpacity={0.7}
>
<View
style={{
@ -58,7 +58,7 @@ export function ApprovedDomainsList({ domains, loading }: Props) {
color={colors.textMuted}
/>
</View>
</Pressable>
</TouchableOpacity>
{expanded ? (
<View

View File

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

View File

@ -1,5 +1,5 @@
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 Svg, { Path } from 'react-native-svg';
import { useColors } from '../../lib/theme';
@ -84,12 +84,10 @@ export function ProfileHeader({
<View style={{ paddingVertical: 24, paddingHorizontal: 20 }}>
{/* Avatar — Pressable in alignSelf:center-Wrapper (Pressable+style-fn ignoriert alignSelf manchmal in RN) */}
<View style={{ alignSelf: 'center' }}>
<Pressable
<TouchableOpacity
onPress={onEditAvatar}
style={({ pressed }) => ({
position: 'relative',
opacity: pressed ? 0.85 : 1,
})}
activeOpacity={0.85}
style={{ position: 'relative' }}
>
<View
style={{
@ -136,21 +134,21 @@ export function ProfileHeader({
>
<Ionicons name="camera" size={14} color="#ffffff" />
</View>
</Pressable>
</TouchableOpacity>
</View>
{/* Nickname — ganze Zeile Pressable in alignSelf:center-Wrapper */}
<View style={{ alignSelf: 'center' }}>
<Pressable
<TouchableOpacity
onPress={onEditNickname}
hitSlop={8}
style={({ pressed }) => ({
activeOpacity={0.5}
style={{
flexDirection: 'row',
alignItems: 'center',
marginTop: 16,
gap: 6,
opacity: pressed ? 0.5 : 1,
})}
}}
>
<Text
style={{
@ -161,7 +159,7 @@ export function ProfileHeader({
>
{nickname}
</Text>
</Pressable>
</TouchableOpacity>
</View>
{/* 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).
KEIN width:'100%' (Konflikt mit alignSelf:stretch in alignItems:center-Kontext war der Bug). */}
{showDemographicsHint ? (
<Pressable
<TouchableOpacity
onPress={onDemographicsHintPress}
hitSlop={6}
style={({ pressed }) => ({
alignSelf: 'center',
marginTop: 16,
opacity: pressed ? 0.7 : 1,
})}
activeOpacity={0.7}
style={{ alignSelf: 'center', marginTop: 16 }}
>
<View
style={{
@ -299,7 +294,7 @@ export function ProfileHeader({
</Text>
</View>
</View>
</Pressable>
</TouchableOpacity>
) : null}
</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';
type Props = {
@ -19,9 +19,9 @@ type CardProps = {
function StatPill({ value, label, onPress }: CardProps) {
const colors = useColors();
return (
<Pressable
<TouchableOpacity
onPress={onPress}
style={({ pressed }) => ({ opacity: pressed ? 0.6 : 1 })}
activeOpacity={0.6}
>
<View
style={{
@ -63,7 +63,7 @@ function StatPill({ value, label, onPress }: CardProps) {
{label}
</Text>
</View>
</Pressable>
</TouchableOpacity>
);
}

View File

@ -1,6 +1,6 @@
// 4-7-8 Atemübung: Card (in-chat) + Drawer (bottom sheet).
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 { useColors } from '../../lib/theme';
@ -87,9 +87,9 @@ export function BreathingCard({ onDone, onSpeak }: Props) {
<View style={{ alignItems: 'center', gap: 16 }}>
<Text style={st.breathTitle}>4-7-8 Atemübung</Text>
<Text style={st.breathSub}>3 Runden · beruhigt dein Nervensystem</Text>
<Pressable style={[st.breathStartBtn, { 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>
</Pressable>
</TouchableOpacity>
</View>
) : breathState === 'countdown' ? (
<View style={{ alignItems: 'center', gap: 20 }}>

View File

@ -3,6 +3,7 @@ import {
View,
Text,
Pressable,
TouchableOpacity,
TextInput,
StyleSheet,
Animated,
@ -80,7 +81,8 @@ export function InlineRatingDrawer({
<Text style={[s.q, { color: colors.textMuted }]}>Fühlst du dich besser?</Text>
<View style={s.btnRow}>
<Pressable
<TouchableOpacity
activeOpacity={0.7}
style={[s.choiceBtn, better === true && s.choiceBtnYes]}
onPress={() => setBetter(true)}
>
@ -90,8 +92,9 @@ export function InlineRatingDrawer({
color={better === true ? '#fff' : '#16a34a'}
/>
<Text style={[s.choiceTxt, better === true && { color: '#fff' }]}>Ja</Text>
</Pressable>
<Pressable
</TouchableOpacity>
<TouchableOpacity
activeOpacity={0.7}
style={[s.choiceBtn, better === false && s.choiceBtnNo]}
onPress={() => setBetter(false)}
>
@ -101,19 +104,19 @@ export function InlineRatingDrawer({
color={better === false ? '#fff' : '#dc2626'}
/>
<Text style={[s.choiceTxt, better === false && { color: '#fff' }]}>Nein</Text>
</Pressable>
</TouchableOpacity>
</View>
<Text style={[s.q, { color: colors.textMuted }]}>Bewertung</Text>
<View style={s.starsRow}>
{[1, 2, 3, 4, 5].map((n) => (
<Pressable key={n} onPress={() => setRating(n)} hitSlop={6}>
<TouchableOpacity key={n} onPress={() => setRating(n)} hitSlop={6} activeOpacity={0.7}>
<Ionicons
name={n <= rating ? 'star' : 'star-outline'}
size={32}
color={n <= rating ? '#f59e0b' : '#cbd5e1'}
/>
</Pressable>
</TouchableOpacity>
))}
</View>
@ -129,16 +132,17 @@ export function InlineRatingDrawer({
/>
<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>
</Pressable>
<Pressable
</TouchableOpacity>
<TouchableOpacity
activeOpacity={0.7}
style={[s.submitBtn, { backgroundColor: colors.brandOrange }, submitting && { opacity: 0.6 }]}
onPress={submit}
disabled={submitting}
>
<Text style={s.submitTxt}>{submitting ? 'Sende…' : 'Senden'}</Text>
</Pressable>
</TouchableOpacity>
</View>
</ScrollView>
</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';
const PROVIDERS: LlmProvider[] = ['auto', 'openrouter-sonnet', 'openrouter-haiku', 'groq-llama'];
@ -30,10 +30,11 @@ export function LlmProviderToggle() {
{PROVIDERS.map((p) => {
const active = p === current;
return (
<Pressable
<TouchableOpacity
key={p}
onPress={() => { void set(p); }}
hitSlop={6}
activeOpacity={0.7}
style={{
paddingHorizontal: 10,
paddingVertical: 4,
@ -52,7 +53,7 @@ export function LlmProviderToggle() {
>
{LLM_PROVIDER_LABEL[p]}
</Text>
</Pressable>
</TouchableOpacity>
);
})}
</View>

View File

@ -1,6 +1,6 @@
// Chat-Bubble + Spezial-Cards (Spiele/Überwunden) für den SOS-Chat-Stream
// 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 { type GameType, GAME_META, GamePickerGrid } from './UrgeGames';
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);
return (
<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={{ 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>

View File

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

View File

@ -1,5 +1,5 @@
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 { useColors } from '../../lib/theme';
@ -51,33 +51,35 @@ export function SosFeedbackModal({
{/* Better Yes/No */}
<Text style={[s.q, { color: colors.textMuted }]}>Fühlst du dich besser?</Text>
<View style={s.btnRow}>
<Pressable
<TouchableOpacity
activeOpacity={0.7}
style={[s.choiceBtn, better === true && s.choiceBtnYes]}
onPress={() => setBetter(true)}
>
<Ionicons name="checkmark-circle" size={18} color={better === true ? '#fff' : '#16a34a'} />
<Text style={[s.choiceTxt, better === true && { color: '#fff' }]}>Ja</Text>
</Pressable>
<Pressable
</TouchableOpacity>
<TouchableOpacity
activeOpacity={0.7}
style={[s.choiceBtn, better === false && s.choiceBtnNo]}
onPress={() => setBetter(false)}
>
<Ionicons name="close-circle" size={18} color={better === false ? '#fff' : '#dc2626'} />
<Text style={[s.choiceTxt, better === false && { color: '#fff' }]}>Nein</Text>
</Pressable>
</TouchableOpacity>
</View>
{/* Stars */}
<Text style={[s.q, { color: colors.textMuted }]}>Bewertung</Text>
<View style={s.starsRow}>
{[1, 2, 3, 4, 5].map((n) => (
<Pressable key={n} onPress={() => setRating(n)} hitSlop={6}>
<TouchableOpacity key={n} onPress={() => setRating(n)} hitSlop={6} activeOpacity={0.7}>
<Ionicons
name={n <= rating ? 'star' : 'star-outline'}
size={32}
color={n <= rating ? '#f59e0b' : '#cbd5e1'}
/>
</Pressable>
</TouchableOpacity>
))}
</View>
@ -96,12 +98,12 @@ export function SosFeedbackModal({
{/* 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>
</Pressable>
<Pressable style={[s.submitBtn, { backgroundColor: colors.brandOrange }]} onPress={submit}>
</TouchableOpacity>
<TouchableOpacity activeOpacity={0.7} style={[s.submitBtn, { backgroundColor: colors.brandOrange }]} onPress={submit}>
<Text style={s.submitTxt}>Senden</Text>
</Pressable>
</TouchableOpacity>
</View>
</View>
</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';
const PROVIDERS: TtsProvider[] = ['openai', 'gemini', 'elevenlabs', 'cartesia', 'google-cloud'];
@ -30,10 +30,11 @@ export function TtsProviderToggle() {
{PROVIDERS.map((p) => {
const active = p === current;
return (
<Pressable
<TouchableOpacity
key={p}
onPress={() => { void set(p); }}
hitSlop={6}
activeOpacity={0.7}
style={{
paddingHorizontal: 10,
paddingVertical: 4,
@ -52,7 +53,7 @@ export function TtsProviderToggle() {
>
{TTS_PROVIDER_LABEL[p]}
</Text>
</Pressable>
</TouchableOpacity>
);
})}
</View>

View File

@ -1,5 +1,5 @@
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 { SvgXml } from 'react-native-svg';
import { Ionicons } from '@expo/vector-icons';
@ -38,13 +38,11 @@ export function GamePickerGrid({ onSelect }: { onSelect: (game: GameType) => voi
return (
<View style={{ flexDirection: 'row', flexWrap: 'wrap', gap: 10 }}>
{GAME_META.map((game) => (
<Pressable
<TouchableOpacity
key={game.id}
onPress={() => onSelect(game.id)}
style={({ pressed }) => ({
width: '47%',
opacity: pressed ? 0.75 : 1,
})}
activeOpacity={0.75}
style={{ width: '47%' }}
>
<View style={{
aspectRatio: 1,
@ -75,7 +73,7 @@ export function GamePickerGrid({ onSelect }: { onSelect: (game: GameType) => voi
{t(game.descKey)}
</Text>
</View>
</Pressable>
</TouchableOpacity>
))}
</View>
);