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:
parent
1ad964f54b
commit
14452b2a46
@ -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>
|
||||
|
||||
|
||||
@ -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>
|
||||
|
||||
@ -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>
|
||||
);
|
||||
}
|
||||
|
||||
|
||||
@ -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>
|
||||
|
||||
@ -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>
|
||||
|
||||
@ -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>
|
||||
);
|
||||
|
||||
@ -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>
|
||||
|
||||
@ -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>
|
||||
|
||||
@ -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>
|
||||
|
||||
@ -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>
|
||||
|
||||
@ -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>
|
||||
)}
|
||||
|
||||
|
||||
@ -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 ? (
|
||||
|
||||
@ -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>
|
||||
|
||||
@ -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')}
|
||||
|
||||
@ -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>
|
||||
|
||||
@ -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>
|
||||
|
||||
|
||||
@ -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>
|
||||
|
||||
@ -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>
|
||||
|
||||
@ -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>
|
||||
|
||||
@ -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} />}
|
||||
|
||||
@ -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>
|
||||
)}
|
||||
|
||||
@ -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>
|
||||
);
|
||||
}
|
||||
|
||||
@ -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>
|
||||
);
|
||||
|
||||
@ -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) */}
|
||||
|
||||
@ -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>
|
||||
);
|
||||
}
|
||||
|
||||
@ -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>
|
||||
);
|
||||
|
||||
@ -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' }}>
|
||||
|
||||
@ -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>
|
||||
|
||||
@ -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>
|
||||
);
|
||||
}
|
||||
|
||||
@ -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 */}
|
||||
|
||||
@ -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>
|
||||
);
|
||||
|
||||
@ -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>
|
||||
);
|
||||
}
|
||||
|
||||
@ -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>
|
||||
|
||||
@ -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 */}
|
||||
|
||||
@ -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}
|
||||
|
||||
@ -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 }}>
|
||||
|
||||
@ -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 */}
|
||||
|
||||
@ -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>
|
||||
</>
|
||||
);
|
||||
|
||||
@ -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>
|
||||
);
|
||||
|
||||
@ -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>
|
||||
|
||||
@ -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>
|
||||
);
|
||||
}
|
||||
|
||||
@ -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>
|
||||
);
|
||||
}
|
||||
|
||||
@ -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>
|
||||
);
|
||||
}
|
||||
|
||||
@ -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>
|
||||
);
|
||||
}
|
||||
|
||||
@ -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);
|
||||
|
||||
@ -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>
|
||||
|
||||
@ -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>
|
||||
);
|
||||
}
|
||||
|
||||
@ -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>
|
||||
);
|
||||
|
||||
@ -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>
|
||||
)}
|
||||
|
||||
@ -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>
|
||||
</>
|
||||
)}
|
||||
|
||||
@ -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}`}
|
||||
/>
|
||||
))}
|
||||
|
||||
@ -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
|
||||
|
||||
@ -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>
|
||||
);
|
||||
}
|
||||
|
||||
@ -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>
|
||||
);
|
||||
|
||||
@ -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>
|
||||
);
|
||||
|
||||
@ -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>
|
||||
);
|
||||
}
|
||||
|
||||
|
||||
@ -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 }}>
|
||||
|
||||
@ -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>
|
||||
|
||||
@ -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>
|
||||
|
||||
@ -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>
|
||||
|
||||
@ -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>
|
||||
|
||||
@ -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>
|
||||
|
||||
@ -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>
|
||||
|
||||
@ -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>
|
||||
);
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user