mirror of
https://github.com/jjbrunton/Sauci.git
synced 2026-11-01 18:19:43 +00:00
Add question types, nudge feature, and share modal
- Add new question card components (photo, audio, text, who-likely) - Add share preview modal for matches - Add nudge notification system with rate limiting - Add session-based notification improvements - Add match confetti celebration animation - Update onboarding flow with paywall tracking - Remove deprecated swipe card and tutorial components - Update GlassCard/GlassInput to use solid backgrounds - Fix routing issues after packs screen removal
This commit is contained in:
1 parent
8612b16b93
commit
1daf386765
114 files changed
+14877
-7911
No files matched your search
@@ -308,7 +308,7 @@ export const ChatScreen: React.FC = () => {
|
||||
|
||||
return (
|
||||
<GradientBackground>
|
||||
{/* Ambient Orbs */}
|
||||
{/* Ambient Orbs - Commented out for flat look
|
||||
<Animated.View style={[styles.ambientOrb, styles.orbTopRight, orbStyle1]} pointerEvents="none">
|
||||
<LinearGradient
|
||||
colors={[colors.premium.goldGlow, 'transparent']}
|
||||
@@ -325,6 +325,7 @@ export const ChatScreen: React.FC = () => {
|
||||
end={{ x: 0, y: 0 }}
|
||||
/>
|
||||
</Animated.View>
|
||||
*/}
|
||||
|
||||
<KeyboardAvoidingView
|
||||
style={styles.keyboardAvoiding}
|
||||
@@ -343,6 +344,8 @@ export const ChatScreen: React.FC = () => {
|
||||
<ChatMessages
|
||||
messages={messages}
|
||||
userId={user?.id}
|
||||
userName={user?.name ?? undefined}
|
||||
partnerName={partner?.name ?? undefined}
|
||||
match={match}
|
||||
uploadStatus={uploadStatus}
|
||||
partnerTyping={partnerTyping}
|
||||
@@ -357,15 +360,20 @@ export const ChatScreen: React.FC = () => {
|
||||
entering={FadeInDown.duration(300)}
|
||||
style={[styles.inputWrapper, { paddingBottom: insets.bottom || spacing.sm }]}
|
||||
>
|
||||
{/* Gradient background */}
|
||||
{/* Flat background */}
|
||||
<View style={[StyleSheet.absoluteFill, { backgroundColor: colors.backgroundLight, borderTopWidth: 1, borderTopColor: colors.border }]} />
|
||||
|
||||
{/* Gradient background - removed
|
||||
<LinearGradient
|
||||
colors={['rgba(22, 33, 62, 0.9)', 'rgba(13, 13, 26, 0.95)']}
|
||||
style={StyleSheet.absoluteFill}
|
||||
start={{ x: 0, y: 0 }}
|
||||
end={{ x: 0, y: 1 }}
|
||||
/>
|
||||
{/* Top border glow */}
|
||||
*/}
|
||||
{/* Top border glow - removed
|
||||
<View style={styles.inputWrapperBorder} />
|
||||
*/}
|
||||
|
||||
<InputBar
|
||||
inputText={inputText}
|
||||
|
||||
@@ -31,20 +31,25 @@ const ChatHeaderComponent: React.FC<ChatHeaderProps> = ({
|
||||
entering={FadeIn.duration(300)}
|
||||
style={[styles.header, { paddingTop: insets.top + spacing.sm }]}
|
||||
>
|
||||
{/* Gradient background */}
|
||||
{/* Flat background */}
|
||||
<View style={[StyleSheet.absoluteFill, { backgroundColor: colors.background, borderBottomWidth: 1, borderBottomColor: colors.border }]} />
|
||||
|
||||
{/* Gradient background - removed
|
||||
<LinearGradient
|
||||
colors={['rgba(22, 33, 62, 0.8)', 'rgba(13, 13, 26, 0.6)']}
|
||||
style={StyleSheet.absoluteFill}
|
||||
start={{ x: 0, y: 0 }}
|
||||
end={{ x: 0, y: 1 }}
|
||||
/>
|
||||
{/* Top silk highlight */}
|
||||
*/}
|
||||
{/* Top silk highlight - removed
|
||||
<LinearGradient
|
||||
colors={[`${ACCENT_RGBA}0.1)`, 'transparent']}
|
||||
style={styles.headerSilkHighlight}
|
||||
start={{ x: 0.5, y: 0 }}
|
||||
end={{ x: 0.5, y: 1 }}
|
||||
/>
|
||||
*/}
|
||||
|
||||
<TouchableOpacity
|
||||
onPress={onBack}
|
||||
@@ -138,12 +143,25 @@ const styles = StyleSheet.create({
|
||||
width: 36,
|
||||
height: 36,
|
||||
borderRadius: 18,
|
||||
backgroundColor: `${ACCENT_RGBA}0.1)`,
|
||||
backgroundColor: colors.backgroundLight,
|
||||
borderWidth: 1,
|
||||
borderColor: `${ACCENT_RGBA}0.2)`,
|
||||
borderColor: colors.border,
|
||||
justifyContent: 'center',
|
||||
alignItems: 'center',
|
||||
},
|
||||
// ...
|
||||
headerSpacer: {
|
||||
width: 40,
|
||||
},
|
||||
settingsButton: {
|
||||
width: 40,
|
||||
height: 40,
|
||||
justifyContent: 'center',
|
||||
alignItems: 'center',
|
||||
},
|
||||
headerBorderBottom: {
|
||||
display: 'none',
|
||||
},
|
||||
headerCenter: {
|
||||
flex: 1,
|
||||
flexDirection: 'row',
|
||||
@@ -195,21 +213,5 @@ const styles = StyleSheet.create({
|
||||
...typography.headline,
|
||||
color: colors.text,
|
||||
},
|
||||
headerSpacer: {
|
||||
width: 40,
|
||||
},
|
||||
settingsButton: {
|
||||
width: 40,
|
||||
height: 40,
|
||||
justifyContent: 'center',
|
||||
alignItems: 'center',
|
||||
},
|
||||
headerBorderBottom: {
|
||||
position: 'absolute',
|
||||
bottom: 0,
|
||||
left: 0,
|
||||
right: 0,
|
||||
height: 1,
|
||||
backgroundColor: `${ACCENT_RGBA}0.15)`,
|
||||
},
|
||||
|
||||
});
|
||||
@@ -19,6 +19,8 @@ type Message = Database['public']['Tables']['messages']['Row'];
|
||||
interface ChatMessagesProps {
|
||||
messages: Message[];
|
||||
userId: string | undefined;
|
||||
userName: string | undefined;
|
||||
partnerName: string | undefined;
|
||||
match: Match | null;
|
||||
uploadStatus: UploadStatus | null;
|
||||
partnerTyping: boolean;
|
||||
@@ -46,6 +48,8 @@ function isSameDay(date1: Date, date2: Date): boolean {
|
||||
const ChatMessagesComponent: React.FC<ChatMessagesProps> = ({
|
||||
messages,
|
||||
userId,
|
||||
userName,
|
||||
partnerName,
|
||||
match,
|
||||
uploadStatus,
|
||||
partnerTyping,
|
||||
@@ -108,9 +112,9 @@ const ChatMessagesComponent: React.FC<ChatMessagesProps> = ({
|
||||
// Memoize ListFooterComponent to prevent recreation
|
||||
const listFooterComponent = useMemo(() => (
|
||||
<View style={styles.footerContainer}>
|
||||
<MatchCard match={match} user={userObject} />
|
||||
<MatchCard match={match} user={userObject} userName={userName} partnerName={partnerName} onImagePress={onImagePress} />
|
||||
</View>
|
||||
), [match, userObject]);
|
||||
), [match, userObject, userName, partnerName, onImagePress]);
|
||||
|
||||
// Memoize ListEmptyComponent to prevent recreation
|
||||
const listEmptyComponent = useMemo(() => (
|
||||
|
||||
@@ -203,45 +203,32 @@ const styles = StyleSheet.create({
|
||||
alignItems: 'center',
|
||||
},
|
||||
plusButton: {
|
||||
backgroundColor: 'rgba(212, 175, 55, 0.1)',
|
||||
backgroundColor: colors.backgroundLight,
|
||||
borderWidth: 1,
|
||||
borderColor: 'rgba(212, 175, 55, 0.2)',
|
||||
},
|
||||
mediaMenuContainer: {
|
||||
flexDirection: 'row',
|
||||
overflow: 'hidden',
|
||||
gap: spacing.xs,
|
||||
},
|
||||
mediaMenuItem: {
|
||||
width: 40,
|
||||
height: 40,
|
||||
justifyContent: 'center',
|
||||
alignItems: 'center',
|
||||
borderColor: colors.border,
|
||||
},
|
||||
// ...
|
||||
mediaMenuItemInner: {
|
||||
width: 36,
|
||||
height: 36,
|
||||
borderRadius: 18,
|
||||
backgroundColor: 'rgba(255, 255, 255, 0.05)',
|
||||
backgroundColor: colors.backgroundLight,
|
||||
borderWidth: 1,
|
||||
borderColor: 'rgba(255, 255, 255, 0.1)',
|
||||
borderColor: colors.border,
|
||||
justifyContent: 'center',
|
||||
alignItems: 'center',
|
||||
},
|
||||
inputFieldWrapper: {
|
||||
flex: 1,
|
||||
position: 'relative',
|
||||
},
|
||||
// ...
|
||||
inputFieldBorder: {
|
||||
...StyleSheet.absoluteFillObject,
|
||||
borderRadius: radius.xl,
|
||||
borderWidth: 1,
|
||||
borderColor: 'rgba(212, 175, 55, 0.15)',
|
||||
borderColor: colors.border,
|
||||
},
|
||||
input: {
|
||||
...typography.body,
|
||||
color: colors.text,
|
||||
backgroundColor: 'rgba(22, 33, 62, 0.6)',
|
||||
backgroundColor: colors.background, // Nested flat background
|
||||
borderRadius: radius.xl,
|
||||
paddingHorizontal: spacing.md,
|
||||
paddingTop: 12,
|
||||
@@ -255,7 +242,27 @@ const styles = StyleSheet.create({
|
||||
justifyContent: 'center',
|
||||
alignItems: 'center',
|
||||
overflow: 'hidden',
|
||||
// Keep gradient for primary action to make it pop
|
||||
},
|
||||
mediaMenuContainer: {
|
||||
flexDirection: 'row',
|
||||
overflow: 'hidden',
|
||||
gap: spacing.xs,
|
||||
},
|
||||
mediaMenuItem: {
|
||||
width: 40,
|
||||
height: 40,
|
||||
justifyContent: 'center',
|
||||
alignItems: 'center',
|
||||
},
|
||||
|
||||
inputFieldWrapper: {
|
||||
flex: 1,
|
||||
position: 'relative',
|
||||
},
|
||||
|
||||
|
||||
|
||||
sendButtonHighlight: {
|
||||
position: 'absolute',
|
||||
top: 0,
|
||||
|
||||
@@ -1,12 +1,16 @@
|
||||
import React, { useRef } from 'react';
|
||||
import { View, Text, StyleSheet } from 'react-native';
|
||||
import React, { useRef, useState, useEffect } from 'react';
|
||||
import { View, Text, StyleSheet, TouchableOpacity, ActivityIndicator } from 'react-native';
|
||||
import { Ionicons } from '@expo/vector-icons';
|
||||
import { LinearGradient } from 'expo-linear-gradient';
|
||||
import { Image } from 'expo-image';
|
||||
import { Audio } from 'expo-av';
|
||||
import Animated, { FadeInDown } from 'react-native-reanimated';
|
||||
|
||||
import { DecorativeSeparator } from '../../../components/ui';
|
||||
import { colors, gradients, spacing, radius, typography } from '../../../theme';
|
||||
import { Match } from '../types';
|
||||
import { ResponseData } from '@sauci/shared';
|
||||
import { getCachedSignedUrl, getStoragePath } from '../../../lib/imageCache';
|
||||
|
||||
// Premium color palette
|
||||
const ACCENT = colors.premium.gold;
|
||||
@@ -15,9 +19,356 @@ const ACCENT_RGBA = 'rgba(212, 175, 55, ';
|
||||
interface MatchCardProps {
|
||||
match: Match | null;
|
||||
user: { id: string } | null;
|
||||
userName?: string;
|
||||
partnerName?: string;
|
||||
onImagePress?: (uri: string) => void;
|
||||
}
|
||||
|
||||
const MatchCardComponent: React.FC<MatchCardProps> = ({ match, user }) => {
|
||||
/**
|
||||
* Helper function to get display text for who_likely choices
|
||||
*/
|
||||
function getChoiceDisplay(chosenUserId: string, userId: string, userName: string, partnerName: string): string {
|
||||
return chosenUserId === userId ? userName : partnerName;
|
||||
}
|
||||
|
||||
/**
|
||||
* Component to render text_answer response summary
|
||||
*/
|
||||
const TextAnswerDisplay: React.FC<{
|
||||
responseSummary: Record<string, ResponseData>;
|
||||
userId: string;
|
||||
partnerName: string;
|
||||
}> = ({ responseSummary, userId, partnerName }) => {
|
||||
const entries = Object.entries(responseSummary);
|
||||
|
||||
return (
|
||||
<View style={styles.responseSummaryContainer}>
|
||||
{entries.map(([responderId, data]) => {
|
||||
if (data.type !== 'text_answer') return null;
|
||||
const isUser = responderId === userId;
|
||||
const name = isUser ? 'You' : partnerName;
|
||||
|
||||
return (
|
||||
<View key={responderId} style={styles.textResponseCard}>
|
||||
<Text style={styles.textResponseName}>{name}</Text>
|
||||
<View style={[
|
||||
styles.textBubble,
|
||||
isUser ? styles.textBubbleUser : styles.textBubblePartner
|
||||
]}>
|
||||
<Text style={styles.textBubbleText}>"{data.text}"</Text>
|
||||
</View>
|
||||
</View>
|
||||
);
|
||||
})}
|
||||
</View>
|
||||
);
|
||||
};
|
||||
|
||||
/**
|
||||
* Component to render photo response summary
|
||||
*/
|
||||
const PhotoDisplay: React.FC<{
|
||||
responseSummary: Record<string, ResponseData>;
|
||||
userId: string;
|
||||
partnerName: string;
|
||||
onImagePress?: (uri: string) => void;
|
||||
}> = ({ responseSummary, userId, partnerName, onImagePress }) => {
|
||||
const [photoUrls, setPhotoUrls] = useState<Record<string, string>>({});
|
||||
const [loadingUrls, setLoadingUrls] = useState(true);
|
||||
const [loadingImages, setLoadingImages] = useState<Record<string, boolean>>({});
|
||||
|
||||
useEffect(() => {
|
||||
const fetchPhotoUrls = async () => {
|
||||
// Fetch all signed URLs in parallel for faster loading
|
||||
const photoEntries: Array<[string, { type: 'photo'; media_path: string }]> = [];
|
||||
for (const [responderId, data] of Object.entries(responseSummary)) {
|
||||
if (data.type === 'photo') {
|
||||
photoEntries.push([responderId, data]);
|
||||
}
|
||||
}
|
||||
|
||||
const urlPromises = photoEntries.map(async ([responderId, data]) => {
|
||||
try {
|
||||
const storagePath = getStoragePath(data.media_path);
|
||||
const signedUrl = await getCachedSignedUrl(storagePath, 'response-media');
|
||||
return { responderId, signedUrl };
|
||||
} catch (err) {
|
||||
console.error('Failed to get photo URL:', err);
|
||||
return { responderId, signedUrl: null };
|
||||
}
|
||||
});
|
||||
|
||||
const results = await Promise.all(urlPromises);
|
||||
const urls: Record<string, string> = {};
|
||||
|
||||
// Collect URLs and prefetch images
|
||||
const prefetchPromises: Promise<boolean>[] = [];
|
||||
for (const { responderId, signedUrl } of results) {
|
||||
if (signedUrl) {
|
||||
urls[responderId] = signedUrl;
|
||||
// Prefetch image for faster display
|
||||
prefetchPromises.push(Image.prefetch(signedUrl));
|
||||
}
|
||||
}
|
||||
|
||||
// Set URLs immediately, prefetch in background
|
||||
setPhotoUrls(urls);
|
||||
setLoadingUrls(false);
|
||||
|
||||
// Initialize loading state for each image
|
||||
const initialLoading: Record<string, boolean> = {};
|
||||
for (const responderId of Object.keys(urls)) {
|
||||
initialLoading[responderId] = true;
|
||||
}
|
||||
setLoadingImages(initialLoading);
|
||||
|
||||
// Wait for prefetch to complete (non-blocking)
|
||||
Promise.all(prefetchPromises).catch(() => {
|
||||
// Prefetch failures are non-critical
|
||||
});
|
||||
};
|
||||
|
||||
fetchPhotoUrls();
|
||||
}, [responseSummary]);
|
||||
|
||||
const handleImageLoad = (responderId: string) => {
|
||||
setLoadingImages(prev => ({ ...prev, [responderId]: false }));
|
||||
};
|
||||
|
||||
const entries = Object.entries(responseSummary);
|
||||
|
||||
return (
|
||||
<View style={styles.photoContainer}>
|
||||
{entries.map(([responderId, data]) => {
|
||||
if (data.type !== 'photo') return null;
|
||||
const isUser = responderId === userId;
|
||||
const name = isUser ? 'You' : partnerName;
|
||||
const photoUrl = photoUrls[responderId];
|
||||
const isImageLoading = loadingImages[responderId];
|
||||
|
||||
return (
|
||||
<View key={responderId} style={styles.photoCard}>
|
||||
<Text style={styles.photoName}>{name}</Text>
|
||||
<TouchableOpacity
|
||||
style={styles.photoThumbnailContainer}
|
||||
onPress={() => photoUrl && onImagePress?.(photoUrl)}
|
||||
disabled={!photoUrl}
|
||||
activeOpacity={0.8}
|
||||
accessibilityRole="button"
|
||||
accessibilityLabel={`View ${name}'s photo full screen`}
|
||||
>
|
||||
{loadingUrls ? (
|
||||
<View style={styles.photoPlaceholder}>
|
||||
<ActivityIndicator size="small" color={colors.premium.gold} />
|
||||
</View>
|
||||
) : photoUrl ? (
|
||||
<>
|
||||
<Image
|
||||
source={{ uri: photoUrl }}
|
||||
style={styles.photoThumbnail}
|
||||
contentFit="cover"
|
||||
cachePolicy="disk"
|
||||
transition={200}
|
||||
onLoad={() => handleImageLoad(responderId)}
|
||||
/>
|
||||
{isImageLoading && (
|
||||
<View style={styles.photoLoadingOverlay}>
|
||||
<ActivityIndicator size="small" color={colors.premium.gold} />
|
||||
</View>
|
||||
)}
|
||||
</>
|
||||
) : (
|
||||
<View style={styles.photoPlaceholder}>
|
||||
<Ionicons name="image-outline" size={24} color={colors.textTertiary} />
|
||||
</View>
|
||||
)}
|
||||
</TouchableOpacity>
|
||||
</View>
|
||||
);
|
||||
})}
|
||||
</View>
|
||||
);
|
||||
};
|
||||
|
||||
/**
|
||||
* Component to render who_likely response summary
|
||||
*/
|
||||
const WhoLikelyDisplay: React.FC<{
|
||||
responseSummary: Record<string, ResponseData>;
|
||||
userId: string;
|
||||
userName: string;
|
||||
partnerName: string;
|
||||
}> = ({ responseSummary, userId, userName, partnerName }) => {
|
||||
const entries = Object.entries(responseSummary);
|
||||
|
||||
return (
|
||||
<View style={styles.whoLikelyContainer}>
|
||||
{entries.map(([responderId, data]) => {
|
||||
if (data.type !== 'who_likely') return null;
|
||||
const isUser = responderId === userId;
|
||||
const voterName = isUser ? 'You' : partnerName;
|
||||
const choiceName = getChoiceDisplay(data.chosen_user_id, userId, userName, partnerName);
|
||||
|
||||
return (
|
||||
<View key={responderId} style={styles.whoLikelyCard}>
|
||||
<Text style={styles.whoLikelyVoter}>{voterName} chose:</Text>
|
||||
<LinearGradient
|
||||
colors={gradients.premiumRose as [string, string]}
|
||||
style={styles.whoLikelyChoiceBadge}
|
||||
start={{ x: 0, y: 0 }}
|
||||
end={{ x: 1, y: 1 }}
|
||||
>
|
||||
<Text style={styles.whoLikelyChoiceText}>{choiceName}</Text>
|
||||
</LinearGradient>
|
||||
</View>
|
||||
);
|
||||
})}
|
||||
</View>
|
||||
);
|
||||
};
|
||||
|
||||
/**
|
||||
* Component to render audio response summary
|
||||
*/
|
||||
const AudioDisplay: React.FC<{
|
||||
responseSummary: Record<string, ResponseData>;
|
||||
userId: string;
|
||||
partnerName: string;
|
||||
}> = ({ responseSummary, userId, partnerName }) => {
|
||||
const [audioUrls, setAudioUrls] = useState<Record<string, string>>({});
|
||||
const [playingId, setPlayingId] = useState<string | null>(null);
|
||||
const [sounds, setSounds] = useState<Record<string, Audio.Sound>>({});
|
||||
|
||||
useEffect(() => {
|
||||
const fetchAudioUrls = async () => {
|
||||
const urls: Record<string, string> = {};
|
||||
|
||||
for (const [responderId, data] of Object.entries(responseSummary)) {
|
||||
if (data.type === 'audio' && data.media_path) {
|
||||
try {
|
||||
const storagePath = getStoragePath(data.media_path);
|
||||
const signedUrl = await getCachedSignedUrl(storagePath, 'response-media');
|
||||
|
||||
if (signedUrl) {
|
||||
urls[responderId] = signedUrl;
|
||||
}
|
||||
} catch (err) {
|
||||
console.error('Failed to get audio URL:', err);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
setAudioUrls(urls);
|
||||
};
|
||||
|
||||
fetchAudioUrls();
|
||||
|
||||
// Cleanup sounds on unmount
|
||||
return () => {
|
||||
Object.values(sounds).forEach(sound => {
|
||||
sound.unloadAsync();
|
||||
});
|
||||
};
|
||||
}, [responseSummary]);
|
||||
|
||||
const formatDuration = (seconds: number) => {
|
||||
const mins = Math.floor(seconds / 60);
|
||||
const secs = Math.floor(seconds % 60);
|
||||
return `${mins}:${secs.toString().padStart(2, '0')}`;
|
||||
};
|
||||
|
||||
const handlePlayPause = async (responderId: string) => {
|
||||
const audioUrl = audioUrls[responderId];
|
||||
if (!audioUrl) return;
|
||||
|
||||
// If already playing this one, pause it
|
||||
if (playingId === responderId && sounds[responderId]) {
|
||||
await sounds[responderId].pauseAsync();
|
||||
setPlayingId(null);
|
||||
return;
|
||||
}
|
||||
|
||||
// Stop any currently playing audio
|
||||
if (playingId && sounds[playingId]) {
|
||||
await sounds[playingId].stopAsync();
|
||||
}
|
||||
|
||||
// Play the selected audio
|
||||
try {
|
||||
let sound = sounds[responderId];
|
||||
if (!sound) {
|
||||
const { sound: newSound } = await Audio.Sound.createAsync(
|
||||
{ uri: audioUrl },
|
||||
{ shouldPlay: false },
|
||||
(status) => {
|
||||
if (status.isLoaded && status.didJustFinish) {
|
||||
setPlayingId(null);
|
||||
}
|
||||
}
|
||||
);
|
||||
sound = newSound;
|
||||
setSounds(prev => ({ ...prev, [responderId]: sound }));
|
||||
}
|
||||
await sound.playFromPositionAsync(0);
|
||||
setPlayingId(responderId);
|
||||
} catch (err) {
|
||||
console.error('Failed to play audio:', err);
|
||||
}
|
||||
};
|
||||
|
||||
const entries = Object.entries(responseSummary);
|
||||
|
||||
return (
|
||||
<View style={styles.audioContainer}>
|
||||
{entries.map(([responderId, data]) => {
|
||||
if (data.type !== 'audio') return null;
|
||||
const isUser = responderId === userId;
|
||||
const name = isUser ? 'You' : partnerName;
|
||||
const audioUrl = audioUrls[responderId];
|
||||
const isPlaying = playingId === responderId;
|
||||
|
||||
return (
|
||||
<View key={responderId} style={styles.audioCard}>
|
||||
<Text style={styles.audioName}>{name}</Text>
|
||||
<TouchableOpacity
|
||||
style={styles.audioPlayer}
|
||||
onPress={() => handlePlayPause(responderId)}
|
||||
disabled={!audioUrl}
|
||||
>
|
||||
<View style={[
|
||||
styles.audioPlayButton,
|
||||
isPlaying && styles.audioPlayButtonActive
|
||||
]}>
|
||||
<Ionicons
|
||||
name={isPlaying ? 'pause' : 'play'}
|
||||
size={20}
|
||||
color={colors.text}
|
||||
/>
|
||||
</View>
|
||||
<View style={styles.audioWaveform}>
|
||||
{[...Array(5)].map((_, i) => (
|
||||
<View
|
||||
key={i}
|
||||
style={[
|
||||
styles.audioBar,
|
||||
{ height: 8 + (i % 3) * 8 },
|
||||
isPlaying && styles.audioBarActive
|
||||
]}
|
||||
/>
|
||||
))}
|
||||
</View>
|
||||
<Text style={styles.audioDuration}>
|
||||
{formatDuration(data.duration_seconds)}
|
||||
</Text>
|
||||
</TouchableOpacity>
|
||||
</View>
|
||||
);
|
||||
})}
|
||||
</View>
|
||||
);
|
||||
};
|
||||
|
||||
const MatchCardComponent: React.FC<MatchCardProps> = ({ match, user, userName = 'You', partnerName = 'Partner', onImagePress }) => {
|
||||
// Track if animation has already played to prevent re-animation on re-renders
|
||||
const hasAnimatedRef = useRef(false);
|
||||
const shouldAnimate = !hasAnimatedRef.current;
|
||||
@@ -29,6 +380,16 @@ const MatchCardComponent: React.FC<MatchCardProps> = ({ match, user }) => {
|
||||
if (!match?.question) return null;
|
||||
|
||||
const isYesYes = match?.match_type === 'yes_yes';
|
||||
const isBothAnswered = match?.match_type === 'both_answered';
|
||||
|
||||
// Determine the response type from response_summary
|
||||
const getResponseType = (): ResponseData['type'] | null => {
|
||||
if (!match.response_summary) return null;
|
||||
const firstResponse = Object.values(match.response_summary)[0];
|
||||
return firstResponse?.type || null;
|
||||
};
|
||||
|
||||
const responseType = getResponseType();
|
||||
|
||||
// Only animate on first render to prevent re-animation when list re-renders
|
||||
const enteringAnimation = shouldAnimate
|
||||
@@ -41,7 +402,7 @@ const MatchCardComponent: React.FC<MatchCardProps> = ({ match, user }) => {
|
||||
style={styles.matchCardContainer}
|
||||
>
|
||||
<View style={styles.matchCard}>
|
||||
{/* Card gradient background */}
|
||||
{/* Card gradient background - removed for flat style
|
||||
<LinearGradient
|
||||
colors={isYesYes
|
||||
? ['rgba(212, 175, 55, 0.12)', 'rgba(184, 134, 11, 0.08)']
|
||||
@@ -51,8 +412,9 @@ const MatchCardComponent: React.FC<MatchCardProps> = ({ match, user }) => {
|
||||
start={{ x: 0, y: 0 }}
|
||||
end={{ x: 1, y: 1 }}
|
||||
/>
|
||||
*/}
|
||||
|
||||
{/* Top glow for YES+YES */}
|
||||
{/* Top glow for YES+YES - removed
|
||||
{isYesYes && (
|
||||
<LinearGradient
|
||||
colors={[`${ACCENT_RGBA}0.15)`, 'transparent']}
|
||||
@@ -61,10 +423,11 @@ const MatchCardComponent: React.FC<MatchCardProps> = ({ match, user }) => {
|
||||
end={{ x: 0.5, y: 1 }}
|
||||
/>
|
||||
)}
|
||||
*/}
|
||||
|
||||
{/* Match Label */}
|
||||
<Text style={[styles.matchLabel, isYesYes && styles.matchLabelYesYes]}>
|
||||
{isYesYes ? "PERFECT MATCH" : "SOFT MATCH"}
|
||||
{isYesYes ? "PERFECT MATCH" : isBothAnswered ? "BOTH ANSWERED" : "SOFT MATCH"}
|
||||
</Text>
|
||||
|
||||
{/* Decorative Separator */}
|
||||
@@ -74,8 +437,57 @@ const MatchCardComponent: React.FC<MatchCardProps> = ({ match, user }) => {
|
||||
marginVertical={spacing.md}
|
||||
/>
|
||||
|
||||
{/* Question and Responses - Show who answered what */}
|
||||
{match.responses && match.responses.length > 0 ? (
|
||||
{/* Question and Responses - Show based on match type and response type */}
|
||||
{isBothAnswered && match.response_summary && responseType ? (
|
||||
// Render response summary for non-swipe question types
|
||||
<View style={styles.responseSummaryWrapper}>
|
||||
{/* Show the question text */}
|
||||
<Text style={styles.matchQuestionText}>
|
||||
"{match.question.text}"
|
||||
</Text>
|
||||
|
||||
<DecorativeSeparator
|
||||
variant="rose"
|
||||
width="40%"
|
||||
marginVertical={spacing.sm}
|
||||
/>
|
||||
|
||||
{/* Render based on response type */}
|
||||
{responseType === 'text_answer' && (
|
||||
<TextAnswerDisplay
|
||||
responseSummary={match.response_summary}
|
||||
userId={user?.id || ''}
|
||||
partnerName={partnerName}
|
||||
/>
|
||||
)}
|
||||
|
||||
{responseType === 'photo' && (
|
||||
<PhotoDisplay
|
||||
responseSummary={match.response_summary}
|
||||
userId={user?.id || ''}
|
||||
partnerName={partnerName}
|
||||
onImagePress={onImagePress}
|
||||
/>
|
||||
)}
|
||||
|
||||
{responseType === 'audio' && (
|
||||
<AudioDisplay
|
||||
responseSummary={match.response_summary}
|
||||
userId={user?.id || ''}
|
||||
partnerName={partnerName}
|
||||
/>
|
||||
)}
|
||||
|
||||
{responseType === 'who_likely' && (
|
||||
<WhoLikelyDisplay
|
||||
responseSummary={match.response_summary}
|
||||
userId={user?.id || ''}
|
||||
userName={userName}
|
||||
partnerName={partnerName}
|
||||
/>
|
||||
)}
|
||||
</View>
|
||||
) : match.responses && match.responses.length > 0 ? (
|
||||
<View style={styles.responsesContainer}>
|
||||
{(() => {
|
||||
// Sort responses by created_at to determine who answered first
|
||||
@@ -134,18 +546,13 @@ const MatchCardComponent: React.FC<MatchCardProps> = ({ match, user }) => {
|
||||
</Text>
|
||||
)}
|
||||
|
||||
{/* Premium border */}
|
||||
<View style={[
|
||||
styles.matchCardBorder,
|
||||
isYesYes && styles.matchCardBorderYesYes
|
||||
]} pointerEvents="none" />
|
||||
{/* Premium border removed */}
|
||||
</View>
|
||||
</Animated.View>
|
||||
);
|
||||
};
|
||||
|
||||
// Wrap with React.memo for performance
|
||||
export const MatchCard = React.memo(MatchCardComponent);
|
||||
// ...
|
||||
|
||||
const styles = StyleSheet.create({
|
||||
matchCardContainer: {
|
||||
@@ -153,6 +560,9 @@ const styles = StyleSheet.create({
|
||||
marginTop: spacing.md,
|
||||
borderRadius: radius.lg,
|
||||
overflow: 'hidden',
|
||||
borderWidth: 1,
|
||||
borderColor: colors.border,
|
||||
backgroundColor: colors.backgroundLight,
|
||||
},
|
||||
matchCard: {
|
||||
borderRadius: radius.lg,
|
||||
@@ -160,14 +570,81 @@ const styles = StyleSheet.create({
|
||||
paddingHorizontal: spacing.md,
|
||||
alignItems: 'center',
|
||||
overflow: 'hidden',
|
||||
backgroundColor: colors.backgroundLight,
|
||||
},
|
||||
matchCardGlow: {
|
||||
position: 'absolute',
|
||||
top: 0,
|
||||
left: 0,
|
||||
right: 0,
|
||||
height: 60,
|
||||
display: 'none',
|
||||
},
|
||||
// ...
|
||||
responseCard: {
|
||||
backgroundColor: colors.background, // Nested
|
||||
borderRadius: radius.md,
|
||||
padding: spacing.sm,
|
||||
borderWidth: 1,
|
||||
borderColor: colors.border,
|
||||
},
|
||||
// ...
|
||||
matchCardBorder: {
|
||||
display: 'none',
|
||||
},
|
||||
matchCardBorderYesYes: {
|
||||
display: 'none',
|
||||
},
|
||||
// ...
|
||||
textBubbleUser: {
|
||||
backgroundColor: colors.background,
|
||||
borderWidth: 1,
|
||||
borderColor: colors.primary,
|
||||
alignSelf: 'flex-end',
|
||||
},
|
||||
textBubblePartner: {
|
||||
backgroundColor: colors.background,
|
||||
borderWidth: 1,
|
||||
borderColor: colors.border,
|
||||
alignSelf: 'flex-start',
|
||||
},
|
||||
// ...
|
||||
photoPlaceholder: {
|
||||
width: '100%',
|
||||
height: '100%',
|
||||
backgroundColor: colors.background,
|
||||
justifyContent: 'center',
|
||||
alignItems: 'center',
|
||||
},
|
||||
// ...
|
||||
whoLikelyCard: {
|
||||
flexDirection: 'row',
|
||||
alignItems: 'center',
|
||||
justifyContent: 'space-between',
|
||||
backgroundColor: colors.background,
|
||||
borderRadius: radius.md,
|
||||
padding: spacing.sm,
|
||||
borderWidth: 1,
|
||||
borderColor: colors.border,
|
||||
},
|
||||
// ...
|
||||
audioPlayer: {
|
||||
flexDirection: 'row',
|
||||
alignItems: 'center',
|
||||
backgroundColor: colors.background,
|
||||
borderRadius: radius.lg,
|
||||
padding: spacing.sm,
|
||||
gap: spacing.sm,
|
||||
borderWidth: 1,
|
||||
borderColor: colors.border,
|
||||
},
|
||||
audioPlayButton: {
|
||||
width: 36,
|
||||
height: 36,
|
||||
borderRadius: 18,
|
||||
backgroundColor: colors.backgroundLight,
|
||||
justifyContent: 'center',
|
||||
alignItems: 'center',
|
||||
borderWidth: 1,
|
||||
borderColor: colors.border,
|
||||
},
|
||||
|
||||
|
||||
matchLabel: {
|
||||
...typography.caption2,
|
||||
fontWeight: '700',
|
||||
@@ -189,13 +666,7 @@ const styles = StyleSheet.create({
|
||||
width: '100%',
|
||||
gap: spacing.sm,
|
||||
},
|
||||
responseCard: {
|
||||
backgroundColor: 'rgba(255, 255, 255, 0.03)',
|
||||
borderRadius: radius.md,
|
||||
padding: spacing.sm,
|
||||
borderWidth: 1,
|
||||
borderColor: 'rgba(255, 255, 255, 0.06)',
|
||||
},
|
||||
|
||||
responseHeader: {
|
||||
flexDirection: 'row',
|
||||
alignItems: 'center',
|
||||
@@ -246,17 +717,151 @@ const styles = StyleSheet.create({
|
||||
fontStyle: 'italic',
|
||||
lineHeight: 18,
|
||||
},
|
||||
matchCardBorder: {
|
||||
position: 'absolute',
|
||||
top: 0,
|
||||
left: 0,
|
||||
right: 0,
|
||||
bottom: 0,
|
||||
borderRadius: radius.lg,
|
||||
borderWidth: 1,
|
||||
borderColor: `rgba(232, 164, 174, 0.2)`,
|
||||
|
||||
|
||||
|
||||
// Response summary styles for non-swipe question types
|
||||
responseSummaryWrapper: {
|
||||
width: '100%',
|
||||
alignItems: 'center',
|
||||
},
|
||||
matchCardBorderYesYes: {
|
||||
borderColor: `${ACCENT_RGBA}0.25)`,
|
||||
responseSummaryContainer: {
|
||||
width: '100%',
|
||||
gap: spacing.sm,
|
||||
},
|
||||
|
||||
// Text answer styles
|
||||
textResponseCard: {
|
||||
width: '100%',
|
||||
gap: spacing.xs,
|
||||
},
|
||||
textResponseName: {
|
||||
...typography.caption2,
|
||||
fontWeight: '600',
|
||||
color: colors.textSecondary,
|
||||
textTransform: 'uppercase',
|
||||
letterSpacing: 1,
|
||||
},
|
||||
textBubble: {
|
||||
paddingHorizontal: spacing.md,
|
||||
paddingVertical: spacing.sm,
|
||||
borderRadius: radius.md,
|
||||
maxWidth: '100%',
|
||||
},
|
||||
|
||||
|
||||
textBubbleText: {
|
||||
...typography.subhead,
|
||||
color: colors.text,
|
||||
fontStyle: 'italic',
|
||||
lineHeight: 20,
|
||||
},
|
||||
|
||||
// Photo styles
|
||||
photoContainer: {
|
||||
flexDirection: 'row',
|
||||
justifyContent: 'center',
|
||||
gap: spacing.md,
|
||||
width: '100%',
|
||||
},
|
||||
photoCard: {
|
||||
alignItems: 'center',
|
||||
gap: spacing.xs,
|
||||
flex: 1,
|
||||
maxWidth: 120,
|
||||
},
|
||||
photoName: {
|
||||
...typography.caption2,
|
||||
fontWeight: '600',
|
||||
color: colors.textSecondary,
|
||||
textTransform: 'uppercase',
|
||||
letterSpacing: 1,
|
||||
},
|
||||
photoThumbnailContainer: {
|
||||
width: 80,
|
||||
height: 80,
|
||||
borderRadius: radius.md,
|
||||
overflow: 'hidden',
|
||||
borderWidth: 2,
|
||||
borderColor: 'rgba(232, 164, 174, 0.3)',
|
||||
},
|
||||
photoThumbnail: {
|
||||
width: '100%',
|
||||
height: '100%',
|
||||
},
|
||||
photoLoadingOverlay: {
|
||||
...StyleSheet.absoluteFillObject,
|
||||
backgroundColor: 'rgba(0, 0, 0, 0.3)',
|
||||
justifyContent: 'center',
|
||||
alignItems: 'center',
|
||||
},
|
||||
|
||||
|
||||
// Who likely styles
|
||||
whoLikelyContainer: {
|
||||
width: '100%',
|
||||
gap: spacing.sm,
|
||||
},
|
||||
|
||||
whoLikelyVoter: {
|
||||
...typography.caption1,
|
||||
fontWeight: '500',
|
||||
color: colors.textSecondary,
|
||||
},
|
||||
whoLikelyChoiceBadge: {
|
||||
paddingHorizontal: spacing.md,
|
||||
paddingVertical: spacing.xs,
|
||||
borderRadius: radius.full,
|
||||
},
|
||||
whoLikelyChoiceText: {
|
||||
...typography.caption1,
|
||||
fontWeight: '700',
|
||||
color: colors.text,
|
||||
letterSpacing: 0.5,
|
||||
},
|
||||
// Audio display styles
|
||||
audioContainer: {
|
||||
width: '100%',
|
||||
gap: spacing.md,
|
||||
},
|
||||
audioCard: {
|
||||
width: '100%',
|
||||
gap: spacing.xs,
|
||||
},
|
||||
audioName: {
|
||||
...typography.caption1,
|
||||
fontWeight: '600',
|
||||
color: colors.textSecondary,
|
||||
textAlign: 'center',
|
||||
},
|
||||
|
||||
|
||||
audioPlayButtonActive: {
|
||||
backgroundColor: colors.primary,
|
||||
},
|
||||
audioWaveform: {
|
||||
flex: 1,
|
||||
flexDirection: 'row',
|
||||
alignItems: 'center',
|
||||
justifyContent: 'center',
|
||||
gap: 3,
|
||||
height: 32,
|
||||
},
|
||||
audioBar: {
|
||||
width: 4,
|
||||
backgroundColor: 'rgba(232, 164, 174, 0.3)',
|
||||
borderRadius: radius.full,
|
||||
},
|
||||
audioBarActive: {
|
||||
backgroundColor: colors.primary,
|
||||
},
|
||||
audioDuration: {
|
||||
...typography.caption1,
|
||||
color: colors.textTertiary,
|
||||
fontVariant: ['tabular-nums'],
|
||||
minWidth: 35,
|
||||
textAlign: 'right',
|
||||
},
|
||||
});
|
||||
|
||||
export { MatchCardComponent as MatchCard };
|
||||
@@ -51,13 +51,14 @@ const MessageBubbleComponent = ({ isMe, index, children, onLongPress }: MessageB
|
||||
start={{ x: 0, y: 0 }}
|
||||
end={{ x: 1, y: 1 }}
|
||||
>
|
||||
{/* Silk highlight */}
|
||||
{/* Silk highlight removed for flat look
|
||||
<LinearGradient
|
||||
colors={['rgba(255, 255, 255, 0.15)', 'transparent']}
|
||||
style={styles.bubbleSilkHighlight}
|
||||
start={{ x: 0.5, y: 0 }}
|
||||
end={{ x: 0.5, y: 1 }}
|
||||
/>
|
||||
*/}
|
||||
{children}
|
||||
</LinearGradient>
|
||||
</TouchableOpacity>
|
||||
@@ -72,20 +73,22 @@ const MessageBubbleComponent = ({ isMe, index, children, onLongPress }: MessageB
|
||||
accessibilityHint="Long press to report"
|
||||
>
|
||||
<View style={[styles.bubble, styles.theirBubble]}>
|
||||
{/* Subtle gradient background */}
|
||||
{/* Subtle gradient background removed
|
||||
<LinearGradient
|
||||
colors={['rgba(22, 33, 62, 0.6)', 'rgba(13, 13, 26, 0.8)']}
|
||||
style={StyleSheet.absoluteFill}
|
||||
start={{ x: 0, y: 0 }}
|
||||
end={{ x: 1, y: 1 }}
|
||||
/>
|
||||
{/* Silk highlight */}
|
||||
*/}
|
||||
{/* Silk highlight removed
|
||||
<LinearGradient
|
||||
colors={[`${ACCENT_RGBA}0.08)`, 'transparent']}
|
||||
style={styles.bubbleSilkHighlight}
|
||||
start={{ x: 0.5, y: 0 }}
|
||||
end={{ x: 0.5, y: 1 }}
|
||||
/>
|
||||
*/}
|
||||
{children}
|
||||
</View>
|
||||
</TouchableOpacity>
|
||||
@@ -126,13 +129,10 @@ const styles = StyleSheet.create({
|
||||
theirBubble: {
|
||||
borderTopLeftRadius: radius.xs,
|
||||
borderWidth: 1,
|
||||
borderColor: `${ACCENT_RGBA}0.15)`,
|
||||
borderColor: colors.border,
|
||||
backgroundColor: colors.backgroundLight,
|
||||
},
|
||||
bubbleSilkHighlight: {
|
||||
position: 'absolute',
|
||||
top: 0,
|
||||
left: 0,
|
||||
right: 0,
|
||||
height: 30,
|
||||
display: 'none',
|
||||
},
|
||||
});
|
||||
@@ -10,7 +10,7 @@ import { Ionicons } from '@expo/vector-icons';
|
||||
import { colors, gradients, spacing, radius } from '../../../theme';
|
||||
import { MessageMeta } from './MessageMeta';
|
||||
import { ChatVideoPlayer } from './ChatVideoPlayer';
|
||||
import { supabase } from '../../../lib/supabase';
|
||||
import { getCachedSignedUrl, getStoragePath } from '../../../lib/imageCache';
|
||||
import type { Message } from '../types';
|
||||
|
||||
const ACCENT = colors.premium.gold;
|
||||
@@ -67,14 +67,17 @@ const MessageContentComponent: React.FC<MessageContentProps> = ({
|
||||
setMediaLoading(true);
|
||||
setMediaError(false);
|
||||
try {
|
||||
const { data, error } = await supabase.storage
|
||||
.from('chat-media')
|
||||
.createSignedUrl(item.media_path!, 3600); // 1 hour expiry
|
||||
const storagePath = getStoragePath(item.media_path!);
|
||||
const signedUrl = await getCachedSignedUrl(storagePath, 'chat-media');
|
||||
|
||||
if (error) throw error;
|
||||
setMediaUri(data.signedUrl);
|
||||
if (!signedUrl) {
|
||||
throw new Error('Missing signed URL for chat media');
|
||||
}
|
||||
|
||||
setMediaUri(signedUrl);
|
||||
} catch (err) {
|
||||
console.error('Failed to get media URL:', err);
|
||||
setMediaUri(null);
|
||||
setMediaError(true);
|
||||
} finally {
|
||||
setMediaLoading(false);
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
import { Database } from '../../types/supabase';
|
||||
import { ResponseData } from '@sauci/shared';
|
||||
|
||||
/** Message type from database */
|
||||
export type Message = Database['public']['Tables']['messages']['Row'];
|
||||
@@ -15,8 +16,10 @@ export interface Match {
|
||||
id: string;
|
||||
question_id: string;
|
||||
couple_id: string;
|
||||
match_type: 'yes_yes' | 'yes_maybe' | 'maybe_maybe';
|
||||
match_type: 'yes_yes' | 'yes_maybe' | 'maybe_maybe' | 'both_answered';
|
||||
created_at: string;
|
||||
/** Summary of both partners' response data (keyed by user_id) for non-swipe questions */
|
||||
response_summary?: Record<string, ResponseData> | null;
|
||||
question?: {
|
||||
id: string;
|
||||
text: string;
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
import React, { useState } from 'react';
|
||||
import { View, Text, StyleSheet, ScrollView, useWindowDimensions, Platform, Alert, TouchableOpacity } from 'react-native';
|
||||
import { BlurView } from 'expo-blur';
|
||||
import { useRouter } from 'expo-router';
|
||||
import { Ionicons } from '@expo/vector-icons';
|
||||
import Constants from 'expo-constants';
|
||||
@@ -9,9 +10,9 @@ import { LinearGradient } from 'expo-linear-gradient';
|
||||
|
||||
import { GradientBackground } from '../../components/ui';
|
||||
import { Paywall } from '../../components/paywall';
|
||||
import { CompactHeader } from '../../components/discovery';
|
||||
import { colors, featureColors, spacing, typography, radius } from '../../theme';
|
||||
import { useAuthStore, useSubscriptionStore } from '../../store';
|
||||
import { resetSwipeTutorial } from '../../lib/swipeTutorialSeen';
|
||||
import { resetMatchesTutorial } from '../../lib/matchesTutorialSeen';
|
||||
import { supabase } from '../../lib/supabase';
|
||||
|
||||
@@ -29,12 +30,13 @@ export function ProfileScreen() {
|
||||
const { width } = useWindowDimensions();
|
||||
const isWideScreen = width > MAX_CONTENT_WIDTH;
|
||||
|
||||
const { user, partner, fetchUser, isAnonymous } = useAuthStore();
|
||||
const { user, partner, couple, fetchUser, isAnonymous } = useAuthStore();
|
||||
const { subscription } = useSubscriptionStore();
|
||||
const settings = useProfileSettings();
|
||||
|
||||
// Paywall state
|
||||
const [showPaywall, setShowPaywall] = useState(false);
|
||||
const [headerHeight, setHeaderHeight] = useState(130);
|
||||
|
||||
// Derived state
|
||||
const isOwnSubscription = subscription.isProUser || user?.is_premium;
|
||||
@@ -61,28 +63,31 @@ export function ProfileScreen() {
|
||||
|
||||
return (
|
||||
<GradientBackground>
|
||||
<View
|
||||
style={[styles.stickyHeader, isWideScreen && styles.stickyHeaderWide]}
|
||||
onLayout={(e) => setHeaderHeight(e.nativeEvent.layout.height)}
|
||||
>
|
||||
<BlurView intensity={80} tint="dark" style={StyleSheet.absoluteFill} />
|
||||
<View style={[StyleSheet.absoluteFill, { backgroundColor: 'rgba(14, 14, 17, 0.85)' }]} />
|
||||
<CompactHeader
|
||||
user={user}
|
||||
partner={partner}
|
||||
couple={couple}
|
||||
label="Settings"
|
||||
showGreeting={false}
|
||||
showPartnerBadge={false}
|
||||
/>
|
||||
</View>
|
||||
|
||||
<ScrollView
|
||||
style={styles.container}
|
||||
contentContainerStyle={[
|
||||
styles.contentContainer,
|
||||
isWideScreen && styles.contentContainerWide,
|
||||
{ paddingTop: headerHeight },
|
||||
]}
|
||||
showsVerticalScrollIndicator={false}
|
||||
>
|
||||
{/* Header with user info (compact) */}
|
||||
<Animated.View
|
||||
entering={FadeInDown.delay(100).duration(500)}
|
||||
style={styles.header}
|
||||
>
|
||||
<Text style={styles.headerLabel}>YOUR SAUCI</Text>
|
||||
<Text style={styles.title}>Settings</Text>
|
||||
<View style={styles.separator}>
|
||||
<View style={styles.separatorLine} />
|
||||
<View style={styles.separatorDiamond} />
|
||||
<View style={styles.separatorLine} />
|
||||
</View>
|
||||
</Animated.View>
|
||||
|
||||
{/* Compact Profile Preview */}
|
||||
<Animated.View
|
||||
entering={FadeInDown.delay(150).duration(500)}
|
||||
@@ -213,16 +218,6 @@ export function ProfileScreen() {
|
||||
showChevron={false}
|
||||
/>
|
||||
<View style={styles.divider} />
|
||||
<MenuItem
|
||||
icon="refresh-outline"
|
||||
label="Reset Swipe Tutorial"
|
||||
description="Show the tutorial again"
|
||||
onPress={async () => {
|
||||
await resetSwipeTutorial();
|
||||
Alert.alert("Success", "Swipe tutorial has been reset.");
|
||||
}}
|
||||
/>
|
||||
<View style={styles.divider} />
|
||||
<MenuItem
|
||||
icon="heart-outline"
|
||||
label="Reset Matches Tutorial"
|
||||
@@ -289,59 +284,31 @@ const styles = StyleSheet.create({
|
||||
width: '100%',
|
||||
maxWidth: MAX_CONTENT_WIDTH,
|
||||
},
|
||||
header: {
|
||||
paddingTop: Platform.OS === 'ios' ? 60 : 40,
|
||||
paddingHorizontal: spacing.lg,
|
||||
paddingBottom: spacing.md,
|
||||
alignItems: 'center',
|
||||
stickyHeader: {
|
||||
position: 'absolute',
|
||||
top: 0,
|
||||
left: 0,
|
||||
right: 0,
|
||||
zIndex: 10,
|
||||
overflow: 'hidden',
|
||||
},
|
||||
headerLabel: {
|
||||
...typography.caption2,
|
||||
fontWeight: '600',
|
||||
letterSpacing: 3,
|
||||
color: colors.secondary,
|
||||
marginBottom: spacing.xs,
|
||||
},
|
||||
title: {
|
||||
...typography.largeTitle,
|
||||
color: colors.text,
|
||||
textAlign: 'center',
|
||||
},
|
||||
separator: {
|
||||
flexDirection: 'row',
|
||||
alignItems: 'center',
|
||||
justifyContent: 'center',
|
||||
marginVertical: spacing.md,
|
||||
width: 100,
|
||||
},
|
||||
separatorLine: {
|
||||
flex: 1,
|
||||
height: 1,
|
||||
backgroundColor: 'rgba(155, 89, 182, 0.3)',
|
||||
},
|
||||
separatorDiamond: {
|
||||
width: 6,
|
||||
height: 6,
|
||||
backgroundColor: colors.secondary,
|
||||
transform: [{ rotate: '45deg' }],
|
||||
marginHorizontal: spacing.sm,
|
||||
opacity: 0.6,
|
||||
stickyHeaderWide: {
|
||||
left: '50%',
|
||||
right: 'auto',
|
||||
width: MAX_CONTENT_WIDTH,
|
||||
transform: [{ translateX: -MAX_CONTENT_WIDTH / 2 }],
|
||||
},
|
||||
// Profile Preview
|
||||
profilePreview: {
|
||||
marginHorizontal: spacing.lg,
|
||||
marginBottom: spacing.lg,
|
||||
backgroundColor: colors.glass.background,
|
||||
backgroundColor: colors.backgroundLight, // Flat background
|
||||
borderRadius: radius.lg,
|
||||
borderWidth: 1,
|
||||
borderColor: colors.glass.border,
|
||||
borderColor: colors.border,
|
||||
overflow: 'hidden',
|
||||
},
|
||||
profilePreviewContent: {
|
||||
flexDirection: 'row',
|
||||
alignItems: 'center',
|
||||
padding: spacing.md,
|
||||
},
|
||||
// ...
|
||||
avatarGradient: {
|
||||
width: 56,
|
||||
height: 56,
|
||||
@@ -349,7 +316,26 @@ const styles = StyleSheet.create({
|
||||
padding: 2,
|
||||
justifyContent: 'center',
|
||||
alignItems: 'center',
|
||||
// Removed shadow
|
||||
},
|
||||
// ...
|
||||
versionBadge: {
|
||||
flexDirection: "row",
|
||||
alignItems: "center",
|
||||
backgroundColor: colors.backgroundLight, // Flat background
|
||||
paddingHorizontal: spacing.md,
|
||||
paddingVertical: spacing.sm,
|
||||
borderRadius: radius.full,
|
||||
gap: spacing.xs,
|
||||
borderWidth: 1,
|
||||
borderColor: colors.border,
|
||||
},
|
||||
profilePreviewContent: {
|
||||
flexDirection: 'row',
|
||||
alignItems: 'center',
|
||||
padding: spacing.md,
|
||||
},
|
||||
|
||||
avatar: {
|
||||
width: '100%',
|
||||
height: '100%',
|
||||
@@ -387,7 +373,7 @@ const styles = StyleSheet.create({
|
||||
},
|
||||
divider: {
|
||||
height: 1,
|
||||
backgroundColor: colors.glass.border,
|
||||
backgroundColor: colors.border,
|
||||
marginVertical: spacing.md,
|
||||
},
|
||||
// Version Badge
|
||||
@@ -395,15 +381,7 @@ const styles = StyleSheet.create({
|
||||
alignItems: "center",
|
||||
marginTop: spacing.lg,
|
||||
},
|
||||
versionBadge: {
|
||||
flexDirection: "row",
|
||||
alignItems: "center",
|
||||
backgroundColor: colors.glass.background,
|
||||
paddingHorizontal: spacing.md,
|
||||
paddingVertical: spacing.sm,
|
||||
borderRadius: radius.full,
|
||||
gap: spacing.xs,
|
||||
},
|
||||
|
||||
version: {
|
||||
...typography.caption1,
|
||||
color: colors.textTertiary,
|
||||
|
||||
@@ -204,11 +204,11 @@ const styles = StyleSheet.create({
|
||||
width: 48,
|
||||
height: 48,
|
||||
borderRadius: 24,
|
||||
backgroundColor: colors.glass.background,
|
||||
backgroundColor: colors.backgroundLight,
|
||||
justifyContent: 'center',
|
||||
alignItems: 'center',
|
||||
borderWidth: 2,
|
||||
borderColor: colors.glass.border,
|
||||
borderColor: colors.border,
|
||||
borderStyle: 'dashed',
|
||||
},
|
||||
rowTextContainer: {
|
||||
@@ -234,16 +234,20 @@ const styles = StyleSheet.create({
|
||||
width: 36,
|
||||
height: 36,
|
||||
borderRadius: 18,
|
||||
backgroundColor: colors.glass.background,
|
||||
backgroundColor: colors.backgroundLight,
|
||||
justifyContent: 'center',
|
||||
alignItems: 'center',
|
||||
borderWidth: 1,
|
||||
borderColor: colors.border,
|
||||
},
|
||||
chevronContainer: {
|
||||
width: 28,
|
||||
height: 28,
|
||||
borderRadius: 14,
|
||||
backgroundColor: colors.glass.background,
|
||||
backgroundColor: colors.backgroundLight,
|
||||
justifyContent: 'center',
|
||||
alignItems: 'center',
|
||||
borderWidth: 1,
|
||||
borderColor: colors.border,
|
||||
},
|
||||
});
|
||||
@@ -2,7 +2,6 @@ import React, { useState } from 'react';
|
||||
import { View, Text, StyleSheet, Modal, Platform, KeyboardAvoidingView, TouchableOpacity, ActivityIndicator, TextInput, ViewStyle } from 'react-native';
|
||||
import { Ionicons } from '@expo/vector-icons';
|
||||
import Animated, { FadeInDown } from 'react-native-reanimated';
|
||||
import { BlurView } from 'expo-blur';
|
||||
import { GlassCard, GlassButton } from '../../../components/ui';
|
||||
import { colors, spacing, radius, typography } from '../../../theme';
|
||||
|
||||
@@ -185,96 +184,90 @@ export const DangerZone: React.FC<DangerZoneProps> = ({
|
||||
animationType="fade"
|
||||
onRequestClose={closeResetModal}
|
||||
>
|
||||
<BlurView
|
||||
intensity={Platform.OS === 'ios' ? 20 : 0}
|
||||
tint="dark"
|
||||
style={StyleSheet.absoluteFill}
|
||||
<KeyboardAvoidingView
|
||||
behavior={Platform.OS === 'ios' ? 'padding' : 'height'}
|
||||
style={styles.modalOverlay}
|
||||
>
|
||||
<KeyboardAvoidingView
|
||||
behavior={Platform.OS === 'ios' ? 'padding' : 'height'}
|
||||
style={styles.modalOverlay}
|
||||
>
|
||||
<View style={styles.modalContent}>
|
||||
<View style={styles.modalHeader}>
|
||||
<View style={styles.warningModalIconContainer}>
|
||||
<Ionicons name="refresh" size={32} color={colors.warning} />
|
||||
</View>
|
||||
<Text style={styles.modalTitle}>Reset Progress?</Text>
|
||||
<View style={styles.modalContent}>
|
||||
<View style={styles.modalHeader}>
|
||||
<View style={styles.warningModalIconContainer}>
|
||||
<Ionicons name="refresh" size={32} color={colors.warning} />
|
||||
</View>
|
||||
<Text style={styles.modalTitle}>Reset Progress?</Text>
|
||||
</View>
|
||||
|
||||
<Text style={styles.modalDescription}>
|
||||
This will permanently delete:
|
||||
</Text>
|
||||
<Text style={styles.modalDescription}>
|
||||
This will permanently delete:
|
||||
</Text>
|
||||
|
||||
<View style={styles.warningList}>
|
||||
<View style={styles.deleteListItem}>
|
||||
<Ionicons name="swap-horizontal" size={16} color={colors.warning} />
|
||||
<Text style={styles.deleteListText}>All your swipes</Text>
|
||||
</View>
|
||||
<View style={styles.deleteListItem}>
|
||||
<Ionicons name="heart" size={16} color={colors.warning} />
|
||||
<Text style={styles.deleteListText}>All matches</Text>
|
||||
</View>
|
||||
<View style={styles.deleteListItem}>
|
||||
<Ionicons name="chatbubbles" size={16} color={colors.warning} />
|
||||
<Text style={styles.deleteListText}>All chat messages</Text>
|
||||
</View>
|
||||
<View style={styles.warningList}>
|
||||
<View style={styles.deleteListItem}>
|
||||
<Ionicons name="swap-horizontal" size={16} color={colors.warning} />
|
||||
<Text style={styles.deleteListText}>All your swipes</Text>
|
||||
</View>
|
||||
|
||||
<View style={styles.keepList}>
|
||||
<View style={styles.deleteListItem}>
|
||||
<Ionicons name="checkmark-circle" size={16} color={colors.success} />
|
||||
<Text style={styles.deleteListText}>Your partner connection stays</Text>
|
||||
</View>
|
||||
<View style={styles.deleteListItem}>
|
||||
<Ionicons name="heart" size={16} color={colors.warning} />
|
||||
<Text style={styles.deleteListText}>All matches</Text>
|
||||
</View>
|
||||
|
||||
<Text style={styles.confirmLabel}>
|
||||
Type <Text style={styles.warningKeyword}>RESET</Text> to confirm:
|
||||
</Text>
|
||||
|
||||
<TextInput
|
||||
style={styles.warningInput}
|
||||
value={confirmText}
|
||||
onChangeText={setConfirmText}
|
||||
placeholder="Type RESET"
|
||||
placeholderTextColor={colors.textTertiary}
|
||||
autoCapitalize="characters"
|
||||
autoCorrect={false}
|
||||
editable={!isResetting}
|
||||
/>
|
||||
|
||||
<View style={styles.modalButtons}>
|
||||
<TouchableOpacity
|
||||
style={styles.cancelButton}
|
||||
onPress={closeResetModal}
|
||||
disabled={isResetting}
|
||||
activeOpacity={0.7}
|
||||
>
|
||||
<Text style={styles.cancelButtonText}>Cancel</Text>
|
||||
</TouchableOpacity>
|
||||
|
||||
<TouchableOpacity
|
||||
style={[
|
||||
styles.warningActionButton,
|
||||
(confirmText !== "RESET" || isResetting) && styles.deleteButtonDisabled,
|
||||
]}
|
||||
onPress={handleResetProgress}
|
||||
disabled={confirmText !== "RESET" || isResetting}
|
||||
activeOpacity={0.7}
|
||||
>
|
||||
{isResetting ? (
|
||||
<ActivityIndicator size="small" color={colors.text} />
|
||||
) : (
|
||||
<>
|
||||
<Ionicons name="refresh" size={16} color={colors.text} />
|
||||
<Text style={styles.deleteButtonText}>Reset</Text>
|
||||
</>
|
||||
)}
|
||||
</TouchableOpacity>
|
||||
<View style={styles.deleteListItem}>
|
||||
<Ionicons name="chatbubbles" size={16} color={colors.warning} />
|
||||
<Text style={styles.deleteListText}>All chat messages</Text>
|
||||
</View>
|
||||
</View>
|
||||
</KeyboardAvoidingView>
|
||||
</BlurView>
|
||||
|
||||
<View style={styles.keepList}>
|
||||
<View style={styles.deleteListItem}>
|
||||
<Ionicons name="checkmark-circle" size={16} color={colors.success} />
|
||||
<Text style={styles.deleteListText}>Your partner connection stays</Text>
|
||||
</View>
|
||||
</View>
|
||||
|
||||
<Text style={styles.confirmLabel}>
|
||||
Type <Text style={styles.warningKeyword}>RESET</Text> to confirm:
|
||||
</Text>
|
||||
|
||||
<TextInput
|
||||
style={styles.warningInput}
|
||||
value={confirmText}
|
||||
onChangeText={setConfirmText}
|
||||
placeholder="Type RESET"
|
||||
placeholderTextColor={colors.textTertiary}
|
||||
autoCapitalize="characters"
|
||||
autoCorrect={false}
|
||||
editable={!isResetting}
|
||||
/>
|
||||
|
||||
<View style={styles.modalButtons}>
|
||||
<TouchableOpacity
|
||||
style={styles.cancelButton}
|
||||
onPress={closeResetModal}
|
||||
disabled={isResetting}
|
||||
activeOpacity={0.7}
|
||||
>
|
||||
<Text style={styles.cancelButtonText}>Cancel</Text>
|
||||
</TouchableOpacity>
|
||||
|
||||
<TouchableOpacity
|
||||
style={[
|
||||
styles.warningActionButton,
|
||||
(confirmText !== "RESET" || isResetting) && styles.deleteButtonDisabled,
|
||||
]}
|
||||
onPress={handleResetProgress}
|
||||
disabled={confirmText !== "RESET" || isResetting}
|
||||
activeOpacity={0.7}
|
||||
>
|
||||
{isResetting ? (
|
||||
<ActivityIndicator size="small" color={colors.text} />
|
||||
) : (
|
||||
<>
|
||||
<Ionicons name="refresh" size={16} color={colors.text} />
|
||||
<Text style={styles.deleteButtonText}>Reset</Text>
|
||||
</>
|
||||
)}
|
||||
</TouchableOpacity>
|
||||
</View>
|
||||
</View>
|
||||
</KeyboardAvoidingView>
|
||||
</Modal>
|
||||
|
||||
{/* Delete Relationship Modal */}
|
||||
@@ -284,93 +277,87 @@ export const DangerZone: React.FC<DangerZoneProps> = ({
|
||||
animationType="fade"
|
||||
onRequestClose={closeRelationshipModal}
|
||||
>
|
||||
<BlurView
|
||||
intensity={Platform.OS === 'ios' ? 20 : 0}
|
||||
tint="dark"
|
||||
style={StyleSheet.absoluteFill}
|
||||
<KeyboardAvoidingView
|
||||
behavior={Platform.OS === 'ios' ? 'padding' : 'height'}
|
||||
style={styles.modalOverlay}
|
||||
>
|
||||
<KeyboardAvoidingView
|
||||
behavior={Platform.OS === 'ios' ? 'padding' : 'height'}
|
||||
style={styles.modalOverlay}
|
||||
>
|
||||
<View style={styles.modalContent}>
|
||||
<View style={styles.modalHeader}>
|
||||
<View style={styles.modalIconContainer}>
|
||||
<Ionicons name="heart-dislike" size={32} color={colors.error} />
|
||||
</View>
|
||||
<Text style={styles.modalTitle}>Delete Relationship Data?</Text>
|
||||
<View style={styles.modalContent}>
|
||||
<View style={styles.modalHeader}>
|
||||
<View style={styles.modalIconContainer}>
|
||||
<Ionicons name="heart-dislike" size={32} color={colors.error} />
|
||||
</View>
|
||||
<Text style={styles.modalTitle}>Delete Relationship Data?</Text>
|
||||
</View>
|
||||
|
||||
<Text style={styles.modalDescription}>
|
||||
This action cannot be undone. This will permanently delete:
|
||||
</Text>
|
||||
<Text style={styles.modalDescription}>
|
||||
This action cannot be undone. This will permanently delete:
|
||||
</Text>
|
||||
|
||||
<View style={styles.deleteList}>
|
||||
<View style={styles.deleteListItem}>
|
||||
<Ionicons name="heart-dislike" size={16} color={colors.error} />
|
||||
<Text style={styles.deleteListText}>Your couple connection</Text>
|
||||
</View>
|
||||
<View style={styles.deleteListItem}>
|
||||
<Ionicons name="checkmark-circle" size={16} color={colors.error} />
|
||||
<Text style={styles.deleteListText}>All matches and responses</Text>
|
||||
</View>
|
||||
<View style={styles.deleteListItem}>
|
||||
<Ionicons name="chatbubbles" size={16} color={colors.error} />
|
||||
<Text style={styles.deleteListText}>All chat messages</Text>
|
||||
</View>
|
||||
<View style={styles.deleteListItem}>
|
||||
<Ionicons name="images" size={16} color={colors.error} />
|
||||
<Text style={styles.deleteListText}>All shared photos</Text>
|
||||
</View>
|
||||
<View style={styles.deleteList}>
|
||||
<View style={styles.deleteListItem}>
|
||||
<Ionicons name="heart-dislike" size={16} color={colors.error} />
|
||||
<Text style={styles.deleteListText}>Your couple connection</Text>
|
||||
</View>
|
||||
|
||||
<Text style={styles.confirmLabel}>
|
||||
Type <Text style={styles.confirmKeyword}>DELETE</Text> to confirm:
|
||||
</Text>
|
||||
|
||||
<TextInput
|
||||
style={styles.confirmInput}
|
||||
value={confirmText}
|
||||
onChangeText={setConfirmText}
|
||||
placeholder="Type DELETE"
|
||||
placeholderTextColor={colors.textTertiary}
|
||||
autoCapitalize="characters"
|
||||
autoCorrect={false}
|
||||
editable={!isDeletingRelationship}
|
||||
/>
|
||||
|
||||
<View style={styles.modalButtons}>
|
||||
<TouchableOpacity
|
||||
style={styles.cancelButton}
|
||||
onPress={closeRelationshipModal}
|
||||
disabled={isDeletingRelationship}
|
||||
activeOpacity={0.7}
|
||||
>
|
||||
<Text style={styles.cancelButtonText}>Cancel</Text>
|
||||
</TouchableOpacity>
|
||||
|
||||
<TouchableOpacity
|
||||
style={[
|
||||
styles.deleteButton,
|
||||
(confirmText !== "DELETE" || isDeletingRelationship) && styles.deleteButtonDisabled,
|
||||
]}
|
||||
onPress={handleDeleteRelationship}
|
||||
disabled={confirmText !== "DELETE" || isDeletingRelationship}
|
||||
activeOpacity={0.7}
|
||||
>
|
||||
{isDeletingRelationship ? (
|
||||
<ActivityIndicator size="small" color={colors.text} />
|
||||
) : (
|
||||
<>
|
||||
<Ionicons name="trash" size={16} color={colors.text} />
|
||||
<Text style={styles.deleteButtonText}>Delete</Text>
|
||||
</>
|
||||
)}
|
||||
</TouchableOpacity>
|
||||
<View style={styles.deleteListItem}>
|
||||
<Ionicons name="checkmark-circle" size={16} color={colors.error} />
|
||||
<Text style={styles.deleteListText}>All matches and responses</Text>
|
||||
</View>
|
||||
<View style={styles.deleteListItem}>
|
||||
<Ionicons name="chatbubbles" size={16} color={colors.error} />
|
||||
<Text style={styles.deleteListText}>All chat messages</Text>
|
||||
</View>
|
||||
<View style={styles.deleteListItem}>
|
||||
<Ionicons name="images" size={16} color={colors.error} />
|
||||
<Text style={styles.deleteListText}>All shared photos</Text>
|
||||
</View>
|
||||
</View>
|
||||
</KeyboardAvoidingView>
|
||||
</BlurView>
|
||||
|
||||
<Text style={styles.confirmLabel}>
|
||||
Type <Text style={styles.confirmKeyword}>DELETE</Text> to confirm:
|
||||
</Text>
|
||||
|
||||
<TextInput
|
||||
style={styles.confirmInput}
|
||||
value={confirmText}
|
||||
onChangeText={setConfirmText}
|
||||
placeholder="Type DELETE"
|
||||
placeholderTextColor={colors.textTertiary}
|
||||
autoCapitalize="characters"
|
||||
autoCorrect={false}
|
||||
editable={!isDeletingRelationship}
|
||||
/>
|
||||
|
||||
<View style={styles.modalButtons}>
|
||||
<TouchableOpacity
|
||||
style={styles.cancelButton}
|
||||
onPress={closeRelationshipModal}
|
||||
disabled={isDeletingRelationship}
|
||||
activeOpacity={0.7}
|
||||
>
|
||||
<Text style={styles.cancelButtonText}>Cancel</Text>
|
||||
</TouchableOpacity>
|
||||
|
||||
<TouchableOpacity
|
||||
style={[
|
||||
styles.deleteButton,
|
||||
(confirmText !== "DELETE" || isDeletingRelationship) && styles.deleteButtonDisabled,
|
||||
]}
|
||||
onPress={handleDeleteRelationship}
|
||||
disabled={confirmText !== "DELETE" || isDeletingRelationship}
|
||||
activeOpacity={0.7}
|
||||
>
|
||||
{isDeletingRelationship ? (
|
||||
<ActivityIndicator size="small" color={colors.text} />
|
||||
) : (
|
||||
<>
|
||||
<Ionicons name="trash" size={16} color={colors.text} />
|
||||
<Text style={styles.deleteButtonText}>Delete</Text>
|
||||
</>
|
||||
)}
|
||||
</TouchableOpacity>
|
||||
</View>
|
||||
</View>
|
||||
</KeyboardAvoidingView>
|
||||
</Modal>
|
||||
|
||||
{/* Delete Account Modal */}
|
||||
@@ -380,101 +367,95 @@ export const DangerZone: React.FC<DangerZoneProps> = ({
|
||||
animationType="fade"
|
||||
onRequestClose={closeAccountModal}
|
||||
>
|
||||
<BlurView
|
||||
intensity={Platform.OS === 'ios' ? 20 : 0}
|
||||
tint="dark"
|
||||
style={StyleSheet.absoluteFill}
|
||||
<KeyboardAvoidingView
|
||||
behavior={Platform.OS === 'ios' ? 'padding' : 'height'}
|
||||
style={styles.modalOverlay}
|
||||
>
|
||||
<KeyboardAvoidingView
|
||||
behavior={Platform.OS === 'ios' ? 'padding' : 'height'}
|
||||
style={styles.modalOverlay}
|
||||
>
|
||||
<View style={styles.modalContent}>
|
||||
<View style={styles.modalHeader}>
|
||||
<View style={styles.modalIconContainer}>
|
||||
<Ionicons name="person-remove" size={32} color={colors.error} />
|
||||
</View>
|
||||
<Text style={styles.modalTitle}>Delete Account?</Text>
|
||||
<View style={styles.modalContent}>
|
||||
<View style={styles.modalHeader}>
|
||||
<View style={styles.modalIconContainer}>
|
||||
<Ionicons name="person-remove" size={32} color={colors.error} />
|
||||
</View>
|
||||
<Text style={styles.modalTitle}>Delete Account?</Text>
|
||||
</View>
|
||||
|
||||
<Text style={styles.modalDescription}>
|
||||
This action cannot be undone. This will permanently delete:
|
||||
</Text>
|
||||
<Text style={styles.modalDescription}>
|
||||
This action cannot be undone. This will permanently delete:
|
||||
</Text>
|
||||
|
||||
<View style={styles.deleteList}>
|
||||
<View style={styles.deleteListItem}>
|
||||
<Ionicons name="person" size={16} color={colors.error} />
|
||||
<Text style={styles.deleteListText}>Your account and profile</Text>
|
||||
</View>
|
||||
{hasRelationship && (
|
||||
<>
|
||||
<View style={styles.deleteListItem}>
|
||||
<Ionicons name="heart-dislike" size={16} color={colors.error} />
|
||||
<Text style={styles.deleteListText}>Your relationship data</Text>
|
||||
</View>
|
||||
<View style={styles.deleteListItem}>
|
||||
<Ionicons name="chatbubbles" size={16} color={colors.error} />
|
||||
<Text style={styles.deleteListText}>All chat messages and photos</Text>
|
||||
</View>
|
||||
</>
|
||||
)}
|
||||
<View style={styles.deleteListItem}>
|
||||
<Ionicons name="key" size={16} color={colors.error} />
|
||||
<Text style={styles.deleteListText}>Your encryption keys</Text>
|
||||
</View>
|
||||
<View style={styles.deleteListItem}>
|
||||
<Ionicons name="cloud-offline" size={16} color={colors.error} />
|
||||
<Text style={styles.deleteListText}>All cloud-stored data</Text>
|
||||
</View>
|
||||
<View style={styles.deleteList}>
|
||||
<View style={styles.deleteListItem}>
|
||||
<Ionicons name="person" size={16} color={colors.error} />
|
||||
<Text style={styles.deleteListText}>Your account and profile</Text>
|
||||
</View>
|
||||
|
||||
<Text style={styles.confirmLabel}>
|
||||
Type <Text style={styles.confirmKeyword}>DELETE</Text> to confirm:
|
||||
</Text>
|
||||
|
||||
<TextInput
|
||||
style={styles.confirmInput}
|
||||
value={confirmText}
|
||||
onChangeText={setConfirmText}
|
||||
placeholder="Type DELETE"
|
||||
placeholderTextColor={colors.textTertiary}
|
||||
autoCapitalize="characters"
|
||||
autoCorrect={false}
|
||||
editable={!isDeletingAccount}
|
||||
/>
|
||||
|
||||
<View style={styles.modalButtons}>
|
||||
<TouchableOpacity
|
||||
style={styles.cancelButton}
|
||||
onPress={closeAccountModal}
|
||||
disabled={isDeletingAccount}
|
||||
activeOpacity={0.7}
|
||||
>
|
||||
<Text style={styles.cancelButtonText}>Cancel</Text>
|
||||
</TouchableOpacity>
|
||||
|
||||
<TouchableOpacity
|
||||
style={[
|
||||
styles.deleteButton,
|
||||
(confirmText !== "DELETE" || isDeletingAccount) && styles.deleteButtonDisabled,
|
||||
]}
|
||||
onPress={handleDeleteAccount}
|
||||
disabled={confirmText !== "DELETE" || isDeletingAccount}
|
||||
activeOpacity={0.7}
|
||||
>
|
||||
{isDeletingAccount ? (
|
||||
<ActivityIndicator size="small" color={colors.text} />
|
||||
) : (
|
||||
<>
|
||||
<Ionicons name="trash" size={16} color={colors.text} />
|
||||
<Text style={styles.deleteButtonText}>Delete Account</Text>
|
||||
</>
|
||||
)}
|
||||
</TouchableOpacity>
|
||||
{hasRelationship && (
|
||||
<>
|
||||
<View style={styles.deleteListItem}>
|
||||
<Ionicons name="heart-dislike" size={16} color={colors.error} />
|
||||
<Text style={styles.deleteListText}>Your relationship data</Text>
|
||||
</View>
|
||||
<View style={styles.deleteListItem}>
|
||||
<Ionicons name="chatbubbles" size={16} color={colors.error} />
|
||||
<Text style={styles.deleteListText}>All chat messages and photos</Text>
|
||||
</View>
|
||||
</>
|
||||
)}
|
||||
<View style={styles.deleteListItem}>
|
||||
<Ionicons name="key" size={16} color={colors.error} />
|
||||
<Text style={styles.deleteListText}>Your encryption keys</Text>
|
||||
</View>
|
||||
<View style={styles.deleteListItem}>
|
||||
<Ionicons name="cloud-offline" size={16} color={colors.error} />
|
||||
<Text style={styles.deleteListText}>All cloud-stored data</Text>
|
||||
</View>
|
||||
</View>
|
||||
</KeyboardAvoidingView>
|
||||
</BlurView>
|
||||
|
||||
<Text style={styles.confirmLabel}>
|
||||
Type <Text style={styles.confirmKeyword}>DELETE</Text> to confirm:
|
||||
</Text>
|
||||
|
||||
<TextInput
|
||||
style={styles.confirmInput}
|
||||
value={confirmText}
|
||||
onChangeText={setConfirmText}
|
||||
placeholder="Type DELETE"
|
||||
placeholderTextColor={colors.textTertiary}
|
||||
autoCapitalize="characters"
|
||||
autoCorrect={false}
|
||||
editable={!isDeletingAccount}
|
||||
/>
|
||||
|
||||
<View style={styles.modalButtons}>
|
||||
<TouchableOpacity
|
||||
style={styles.cancelButton}
|
||||
onPress={closeAccountModal}
|
||||
disabled={isDeletingAccount}
|
||||
activeOpacity={0.7}
|
||||
>
|
||||
<Text style={styles.cancelButtonText}>Cancel</Text>
|
||||
</TouchableOpacity>
|
||||
|
||||
<TouchableOpacity
|
||||
style={[
|
||||
styles.deleteButton,
|
||||
(confirmText !== "DELETE" || isDeletingAccount) && styles.deleteButtonDisabled,
|
||||
]}
|
||||
onPress={handleDeleteAccount}
|
||||
disabled={confirmText !== "DELETE" || isDeletingAccount}
|
||||
activeOpacity={0.7}
|
||||
>
|
||||
{isDeletingAccount ? (
|
||||
<ActivityIndicator size="small" color={colors.text} />
|
||||
) : (
|
||||
<>
|
||||
<Ionicons name="trash" size={16} color={colors.text} />
|
||||
<Text style={styles.deleteButtonText}>Delete Account</Text>
|
||||
</>
|
||||
)}
|
||||
</TouchableOpacity>
|
||||
</View>
|
||||
</View>
|
||||
</KeyboardAvoidingView>
|
||||
</Modal>
|
||||
</>
|
||||
);
|
||||
@@ -577,7 +558,7 @@ const styles = StyleSheet.create({
|
||||
backgroundColor: colors.backgroundLight,
|
||||
borderRadius: radius.xl,
|
||||
borderWidth: 1,
|
||||
borderColor: colors.glass.border,
|
||||
borderColor: colors.border,
|
||||
},
|
||||
modalHeader: {
|
||||
alignItems: "center",
|
||||
@@ -644,7 +625,7 @@ const styles = StyleSheet.create({
|
||||
color: colors.warning,
|
||||
},
|
||||
warningInput: {
|
||||
backgroundColor: colors.glass.background,
|
||||
backgroundColor: colors.background,
|
||||
borderRadius: radius.md,
|
||||
borderWidth: 1,
|
||||
borderColor: colors.warning,
|
||||
@@ -684,7 +665,7 @@ const styles = StyleSheet.create({
|
||||
color: colors.error,
|
||||
},
|
||||
confirmInput: {
|
||||
backgroundColor: colors.glass.background,
|
||||
backgroundColor: colors.background,
|
||||
borderRadius: radius.md,
|
||||
borderWidth: 1,
|
||||
borderColor: colors.error,
|
||||
@@ -700,12 +681,12 @@ const styles = StyleSheet.create({
|
||||
},
|
||||
cancelButton: {
|
||||
flex: 1,
|
||||
backgroundColor: colors.glass.backgroundLight,
|
||||
backgroundColor: colors.background,
|
||||
paddingVertical: spacing.md,
|
||||
borderRadius: radius.md,
|
||||
alignItems: "center",
|
||||
borderWidth: 1,
|
||||
borderColor: colors.glass.borderLight,
|
||||
borderColor: colors.border,
|
||||
},
|
||||
cancelButtonText: {
|
||||
...typography.body,
|
||||
|
||||
@@ -127,6 +127,8 @@ const styles = StyleSheet.create({
|
||||
alignItems: 'center',
|
||||
marginRight: spacing.md,
|
||||
backgroundColor: 'rgba(239, 68, 68, 0.1)',
|
||||
borderWidth: 1,
|
||||
borderColor: 'rgba(239, 68, 68, 0.2)', // Added border
|
||||
},
|
||||
textContainer: {
|
||||
flex: 1,
|
||||
|
||||
@@ -34,6 +34,7 @@ export const NotificationSettings: React.FC<NotificationSettingsProps> = ({
|
||||
onMatchesToggle: (value: boolean) => updatePreference('matches_enabled', value),
|
||||
onMessagesToggle: (value: boolean) => updatePreference('messages_enabled', value),
|
||||
onPartnerActivityToggle: (value: boolean) => updatePreference('partner_activity_enabled', value),
|
||||
onNudgesToggle: (value: boolean) => updatePreference('nudges_enabled', value),
|
||||
onPackChangesToggle: (value: boolean) => updatePreference('pack_changes_enabled', value),
|
||||
onNewPacksToggle: (value: boolean) => updatePreference('new_packs_enabled', value),
|
||||
onStreakMilestonesToggle: (value: boolean) => updatePreference('streak_milestones_enabled', value),
|
||||
@@ -91,6 +92,17 @@ export const NotificationSettings: React.FC<NotificationSettingsProps> = ({
|
||||
|
||||
<View style={styles.itemDivider} />
|
||||
|
||||
<SwitchItem
|
||||
icon="hand-left-outline"
|
||||
label="Partner Nudges"
|
||||
description="When your partner sends you a reminder"
|
||||
value={preferences.nudges_enabled}
|
||||
onValueChange={handlers.onNudgesToggle}
|
||||
disabled={isUpdating}
|
||||
/>
|
||||
|
||||
<View style={styles.itemDivider} />
|
||||
|
||||
<SwitchItem
|
||||
icon="layers-outline"
|
||||
label="Pack Updates"
|
||||
@@ -133,7 +145,7 @@ const styles = StyleSheet.create({
|
||||
marginBottom: spacing.md,
|
||||
paddingTop: spacing.md,
|
||||
borderTopWidth: 1,
|
||||
borderTopColor: colors.glass.border,
|
||||
borderTopColor: colors.border,
|
||||
},
|
||||
subsectionLabel: {
|
||||
...typography.caption1,
|
||||
@@ -144,7 +156,7 @@ const styles = StyleSheet.create({
|
||||
},
|
||||
itemDivider: {
|
||||
height: 1,
|
||||
backgroundColor: colors.glass.border,
|
||||
backgroundColor: colors.border,
|
||||
marginVertical: spacing.sm,
|
||||
},
|
||||
});
|
||||
@@ -58,7 +58,7 @@ export const PrivacySettings: React.FC<PrivacySettingsProps> = ({
|
||||
const styles = StyleSheet.create({
|
||||
divider: {
|
||||
height: 1,
|
||||
backgroundColor: colors.glass.border,
|
||||
backgroundColor: colors.border,
|
||||
marginVertical: spacing.lg,
|
||||
},
|
||||
});
|
||||
@@ -155,11 +155,32 @@ const styles = StyleSheet.create({
|
||||
padding: 3,
|
||||
justifyContent: 'center',
|
||||
alignItems: 'center',
|
||||
shadowColor: featureColors.profile.accent,
|
||||
shadowOffset: { width: 0, height: 4 },
|
||||
shadowOpacity: 0.3,
|
||||
shadowRadius: 8,
|
||||
elevation: 6,
|
||||
// Removed shadows for flat look
|
||||
},
|
||||
// ...
|
||||
avatarEditBadge: {
|
||||
position: 'absolute',
|
||||
bottom: 0,
|
||||
right: 0,
|
||||
backgroundColor: colors.backgroundLight,
|
||||
width: 28,
|
||||
height: 28,
|
||||
borderRadius: 14,
|
||||
justifyContent: 'center',
|
||||
alignItems: 'center',
|
||||
borderWidth: 2,
|
||||
borderColor: colors.background,
|
||||
},
|
||||
// ...
|
||||
cancelEditButton: {
|
||||
width: 32,
|
||||
height: 32,
|
||||
borderRadius: 16,
|
||||
backgroundColor: colors.backgroundLight,
|
||||
justifyContent: 'center',
|
||||
alignItems: 'center',
|
||||
borderWidth: 1,
|
||||
borderColor: colors.border,
|
||||
},
|
||||
avatarImage: {
|
||||
width: '100%',
|
||||
@@ -191,19 +212,7 @@ const styles = StyleSheet.create({
|
||||
justifyContent: 'center',
|
||||
alignItems: 'center',
|
||||
},
|
||||
avatarEditBadge: {
|
||||
position: 'absolute',
|
||||
bottom: 0,
|
||||
right: 0,
|
||||
backgroundColor: colors.surfaceSolid,
|
||||
width: 28,
|
||||
height: 28,
|
||||
borderRadius: 14,
|
||||
justifyContent: 'center',
|
||||
alignItems: 'center',
|
||||
borderWidth: 2,
|
||||
borderColor: colors.background,
|
||||
},
|
||||
|
||||
profileInfo: {
|
||||
flex: 1,
|
||||
justifyContent: 'center',
|
||||
@@ -253,12 +262,5 @@ const styles = StyleSheet.create({
|
||||
alignItems: 'center',
|
||||
marginLeft: spacing.xs,
|
||||
},
|
||||
cancelEditButton: {
|
||||
width: 32,
|
||||
height: 32,
|
||||
borderRadius: 16,
|
||||
backgroundColor: colors.glass.highlight,
|
||||
justifyContent: 'center',
|
||||
alignItems: 'center',
|
||||
},
|
||||
|
||||
});
|
||||
@@ -2,8 +2,7 @@ import React, { useState } from 'react';
|
||||
import { View, Text, StyleSheet, Modal, Platform, KeyboardAvoidingView, TouchableOpacity, ActivityIndicator, TextInput, ViewStyle } from 'react-native';
|
||||
import { Ionicons } from '@expo/vector-icons';
|
||||
import Animated, { FadeInDown } from 'react-native-reanimated';
|
||||
import { BlurView } from 'expo-blur';
|
||||
import { GlassCard, GlassButton } from '../../../components/ui';
|
||||
import { GlassCard, GlassButton } from '../../../components/ui';
|
||||
import { colors, spacing, radius, typography } from '../../../theme';
|
||||
|
||||
interface RelationshipDangerZoneProps {
|
||||
@@ -132,199 +131,187 @@ export const RelationshipDangerZone: React.FC<RelationshipDangerZoneProps> = ({
|
||||
</Animated.View>
|
||||
|
||||
{/* Reset Progress Modal */}
|
||||
<Modal
|
||||
visible={showResetModal}
|
||||
transparent
|
||||
animationType="fade"
|
||||
onRequestClose={closeResetModal}
|
||||
>
|
||||
<BlurView
|
||||
intensity={Platform.OS === 'ios' ? 20 : 0}
|
||||
tint="dark"
|
||||
style={StyleSheet.absoluteFill}
|
||||
>
|
||||
<KeyboardAvoidingView
|
||||
behavior={Platform.OS === 'ios' ? 'padding' : 'height'}
|
||||
style={styles.modalOverlay}
|
||||
>
|
||||
<View style={styles.modalContent}>
|
||||
<View style={styles.modalHeader}>
|
||||
<View style={styles.warningModalIconContainer}>
|
||||
<Ionicons name="refresh" size={32} color={colors.warning} />
|
||||
</View>
|
||||
<Text style={styles.modalTitle}>Reset Progress?</Text>
|
||||
</View>
|
||||
|
||||
<Text style={styles.modalDescription}>
|
||||
This will permanently delete:
|
||||
</Text>
|
||||
|
||||
<View style={styles.warningList}>
|
||||
<View style={styles.deleteListItem}>
|
||||
<Ionicons name="swap-horizontal" size={16} color={colors.warning} />
|
||||
<Text style={styles.deleteListText}>All your swipes</Text>
|
||||
</View>
|
||||
<View style={styles.deleteListItem}>
|
||||
<Ionicons name="heart" size={16} color={colors.warning} />
|
||||
<Text style={styles.deleteListText}>All matches</Text>
|
||||
</View>
|
||||
<View style={styles.deleteListItem}>
|
||||
<Ionicons name="chatbubbles" size={16} color={colors.warning} />
|
||||
<Text style={styles.deleteListText}>All chat messages</Text>
|
||||
</View>
|
||||
</View>
|
||||
|
||||
<View style={styles.keepList}>
|
||||
<View style={styles.deleteListItem}>
|
||||
<Ionicons name="checkmark-circle" size={16} color={colors.success} />
|
||||
<Text style={styles.deleteListText}>Your partner connection stays</Text>
|
||||
</View>
|
||||
</View>
|
||||
|
||||
<Text style={styles.confirmLabel}>
|
||||
Type <Text style={styles.warningKeyword}>RESET</Text> to confirm:
|
||||
</Text>
|
||||
|
||||
<TextInput
|
||||
style={styles.warningInput}
|
||||
value={confirmText}
|
||||
onChangeText={setConfirmText}
|
||||
placeholder="Type RESET"
|
||||
placeholderTextColor={colors.textTertiary}
|
||||
autoCapitalize="characters"
|
||||
autoCorrect={false}
|
||||
editable={!isResetting}
|
||||
/>
|
||||
|
||||
<View style={styles.modalButtons}>
|
||||
<TouchableOpacity
|
||||
style={styles.cancelButton}
|
||||
onPress={closeResetModal}
|
||||
disabled={isResetting}
|
||||
activeOpacity={0.7}
|
||||
>
|
||||
<Text style={styles.cancelButtonText}>Cancel</Text>
|
||||
</TouchableOpacity>
|
||||
|
||||
<TouchableOpacity
|
||||
style={[
|
||||
styles.warningActionButton,
|
||||
(confirmText !== "RESET" || isResetting) && styles.deleteButtonDisabled,
|
||||
]}
|
||||
onPress={handleResetProgress}
|
||||
disabled={confirmText !== "RESET" || isResetting}
|
||||
activeOpacity={0.7}
|
||||
>
|
||||
{isResetting ? (
|
||||
<ActivityIndicator size="small" color={colors.text} />
|
||||
) : (
|
||||
<>
|
||||
<Ionicons name="refresh" size={16} color={colors.text} />
|
||||
<Text style={styles.deleteButtonText}>Reset</Text>
|
||||
</>
|
||||
)}
|
||||
</TouchableOpacity>
|
||||
</View>
|
||||
</View>
|
||||
</KeyboardAvoidingView>
|
||||
</BlurView>
|
||||
</Modal>
|
||||
<Modal
|
||||
visible={showResetModal}
|
||||
transparent
|
||||
animationType="fade"
|
||||
onRequestClose={closeResetModal}
|
||||
>
|
||||
<KeyboardAvoidingView
|
||||
behavior={Platform.OS === 'ios' ? 'padding' : 'height'}
|
||||
style={styles.modalOverlay}
|
||||
>
|
||||
<View style={styles.modalContent}>
|
||||
<View style={styles.modalHeader}>
|
||||
<View style={styles.warningModalIconContainer}>
|
||||
<Ionicons name="refresh" size={32} color={colors.warning} />
|
||||
</View>
|
||||
<Text style={styles.modalTitle}>Reset Progress?</Text>
|
||||
</View>
|
||||
|
||||
<Text style={styles.modalDescription}>
|
||||
This will permanently delete:
|
||||
</Text>
|
||||
|
||||
<View style={styles.warningList}>
|
||||
<View style={styles.deleteListItem}>
|
||||
<Ionicons name="swap-horizontal" size={16} color={colors.warning} />
|
||||
<Text style={styles.deleteListText}>All your swipes</Text>
|
||||
</View>
|
||||
<View style={styles.deleteListItem}>
|
||||
<Ionicons name="heart" size={16} color={colors.warning} />
|
||||
<Text style={styles.deleteListText}>All matches</Text>
|
||||
</View>
|
||||
<View style={styles.deleteListItem}>
|
||||
<Ionicons name="chatbubbles" size={16} color={colors.warning} />
|
||||
<Text style={styles.deleteListText}>All chat messages</Text>
|
||||
</View>
|
||||
</View>
|
||||
|
||||
<View style={styles.keepList}>
|
||||
<View style={styles.deleteListItem}>
|
||||
<Ionicons name="checkmark-circle" size={16} color={colors.success} />
|
||||
<Text style={styles.deleteListText}>Your partner connection stays</Text>
|
||||
</View>
|
||||
</View>
|
||||
|
||||
<Text style={styles.confirmLabel}>
|
||||
Type <Text style={styles.warningKeyword}>RESET</Text> to confirm:
|
||||
</Text>
|
||||
|
||||
<TextInput
|
||||
style={styles.warningInput}
|
||||
value={confirmText}
|
||||
onChangeText={setConfirmText}
|
||||
placeholder="Type RESET"
|
||||
placeholderTextColor={colors.textTertiary}
|
||||
autoCapitalize="characters"
|
||||
autoCorrect={false}
|
||||
editable={!isResetting}
|
||||
/>
|
||||
|
||||
<View style={styles.modalButtons}>
|
||||
<TouchableOpacity
|
||||
style={styles.cancelButton}
|
||||
onPress={closeResetModal}
|
||||
disabled={isResetting}
|
||||
activeOpacity={0.7}
|
||||
>
|
||||
<Text style={styles.cancelButtonText}>Cancel</Text>
|
||||
</TouchableOpacity>
|
||||
|
||||
<TouchableOpacity
|
||||
style={[
|
||||
styles.warningActionButton,
|
||||
(confirmText !== "RESET" || isResetting) && styles.deleteButtonDisabled,
|
||||
]}
|
||||
onPress={handleResetProgress}
|
||||
disabled={confirmText !== "RESET" || isResetting}
|
||||
activeOpacity={0.7}
|
||||
>
|
||||
{isResetting ? (
|
||||
<ActivityIndicator size="small" color={colors.text} />
|
||||
) : (
|
||||
<>
|
||||
<Ionicons name="refresh" size={16} color={colors.text} />
|
||||
<Text style={styles.deleteButtonText}>Reset</Text>
|
||||
</>
|
||||
)}
|
||||
</TouchableOpacity>
|
||||
</View>
|
||||
</View>
|
||||
</KeyboardAvoidingView>
|
||||
</Modal>
|
||||
|
||||
{/* Delete Relationship Modal */}
|
||||
<Modal
|
||||
visible={showDeleteRelationshipModal}
|
||||
transparent
|
||||
animationType="fade"
|
||||
onRequestClose={closeRelationshipModal}
|
||||
>
|
||||
<BlurView
|
||||
intensity={Platform.OS === 'ios' ? 20 : 0}
|
||||
tint="dark"
|
||||
style={StyleSheet.absoluteFill}
|
||||
>
|
||||
<KeyboardAvoidingView
|
||||
behavior={Platform.OS === 'ios' ? 'padding' : 'height'}
|
||||
style={styles.modalOverlay}
|
||||
>
|
||||
<View style={styles.modalContent}>
|
||||
<View style={styles.modalHeader}>
|
||||
<View style={styles.modalIconContainer}>
|
||||
<Ionicons name="heart-dislike" size={32} color={colors.error} />
|
||||
</View>
|
||||
<Text style={styles.modalTitle}>Delete Relationship Data?</Text>
|
||||
</View>
|
||||
|
||||
<Text style={styles.modalDescription}>
|
||||
This action cannot be undone. This will permanently delete:
|
||||
</Text>
|
||||
|
||||
<View style={styles.deleteList}>
|
||||
<View style={styles.deleteListItem}>
|
||||
<Ionicons name="heart-dislike" size={16} color={colors.error} />
|
||||
<Text style={styles.deleteListText}>Your couple connection</Text>
|
||||
</View>
|
||||
<View style={styles.deleteListItem}>
|
||||
<Ionicons name="checkmark-circle" size={16} color={colors.error} />
|
||||
<Text style={styles.deleteListText}>All matches and responses</Text>
|
||||
</View>
|
||||
<View style={styles.deleteListItem}>
|
||||
<Ionicons name="chatbubbles" size={16} color={colors.error} />
|
||||
<Text style={styles.deleteListText}>All chat messages</Text>
|
||||
</View>
|
||||
<View style={styles.deleteListItem}>
|
||||
<Ionicons name="images" size={16} color={colors.error} />
|
||||
<Text style={styles.deleteListText}>All shared photos</Text>
|
||||
</View>
|
||||
</View>
|
||||
|
||||
<Text style={styles.confirmLabel}>
|
||||
Type <Text style={styles.confirmKeyword}>DELETE</Text> to confirm:
|
||||
</Text>
|
||||
|
||||
<TextInput
|
||||
style={styles.confirmInput}
|
||||
value={confirmText}
|
||||
onChangeText={setConfirmText}
|
||||
placeholder="Type DELETE"
|
||||
placeholderTextColor={colors.textTertiary}
|
||||
autoCapitalize="characters"
|
||||
autoCorrect={false}
|
||||
editable={!isDeletingRelationship}
|
||||
/>
|
||||
|
||||
<View style={styles.modalButtons}>
|
||||
<TouchableOpacity
|
||||
style={styles.cancelButton}
|
||||
onPress={closeRelationshipModal}
|
||||
disabled={isDeletingRelationship}
|
||||
activeOpacity={0.7}
|
||||
>
|
||||
<Text style={styles.cancelButtonText}>Cancel</Text>
|
||||
</TouchableOpacity>
|
||||
|
||||
<TouchableOpacity
|
||||
style={[
|
||||
styles.deleteButton,
|
||||
(confirmText !== "DELETE" || isDeletingRelationship) && styles.deleteButtonDisabled,
|
||||
]}
|
||||
onPress={handleDeleteRelationship}
|
||||
disabled={confirmText !== "DELETE" || isDeletingRelationship}
|
||||
activeOpacity={0.7}
|
||||
>
|
||||
{isDeletingRelationship ? (
|
||||
<ActivityIndicator size="small" color={colors.text} />
|
||||
) : (
|
||||
<>
|
||||
<Ionicons name="trash" size={16} color={colors.text} />
|
||||
<Text style={styles.deleteButtonText}>Delete</Text>
|
||||
</>
|
||||
)}
|
||||
</TouchableOpacity>
|
||||
</View>
|
||||
</View>
|
||||
</KeyboardAvoidingView>
|
||||
</BlurView>
|
||||
</Modal>
|
||||
<Modal
|
||||
visible={showDeleteRelationshipModal}
|
||||
transparent
|
||||
animationType="fade"
|
||||
onRequestClose={closeRelationshipModal}
|
||||
>
|
||||
<KeyboardAvoidingView
|
||||
behavior={Platform.OS === 'ios' ? 'padding' : 'height'}
|
||||
style={styles.modalOverlay}
|
||||
>
|
||||
<View style={styles.modalContent}>
|
||||
<View style={styles.modalHeader}>
|
||||
<View style={styles.modalIconContainer}>
|
||||
<Ionicons name="heart-dislike" size={32} color={colors.error} />
|
||||
</View>
|
||||
<Text style={styles.modalTitle}>Delete Relationship Data?</Text>
|
||||
</View>
|
||||
|
||||
<Text style={styles.modalDescription}>
|
||||
This action cannot be undone. This will permanently delete:
|
||||
</Text>
|
||||
|
||||
<View style={styles.deleteList}>
|
||||
<View style={styles.deleteListItem}>
|
||||
<Ionicons name="heart-dislike" size={16} color={colors.error} />
|
||||
<Text style={styles.deleteListText}>Your couple connection</Text>
|
||||
</View>
|
||||
<View style={styles.deleteListItem}>
|
||||
<Ionicons name="checkmark-circle" size={16} color={colors.error} />
|
||||
<Text style={styles.deleteListText}>All matches and responses</Text>
|
||||
</View>
|
||||
<View style={styles.deleteListItem}>
|
||||
<Ionicons name="chatbubbles" size={16} color={colors.error} />
|
||||
<Text style={styles.deleteListText}>All chat messages</Text>
|
||||
</View>
|
||||
<View style={styles.deleteListItem}>
|
||||
<Ionicons name="images" size={16} color={colors.error} />
|
||||
<Text style={styles.deleteListText}>All shared photos</Text>
|
||||
</View>
|
||||
</View>
|
||||
|
||||
<Text style={styles.confirmLabel}>
|
||||
Type <Text style={styles.confirmKeyword}>DELETE</Text> to confirm:
|
||||
</Text>
|
||||
|
||||
<TextInput
|
||||
style={styles.confirmInput}
|
||||
value={confirmText}
|
||||
onChangeText={setConfirmText}
|
||||
placeholder="Type DELETE"
|
||||
placeholderTextColor={colors.textTertiary}
|
||||
autoCapitalize="characters"
|
||||
autoCorrect={false}
|
||||
editable={!isDeletingRelationship}
|
||||
/>
|
||||
|
||||
<View style={styles.modalButtons}>
|
||||
<TouchableOpacity
|
||||
style={styles.cancelButton}
|
||||
onPress={closeRelationshipModal}
|
||||
disabled={isDeletingRelationship}
|
||||
activeOpacity={0.7}
|
||||
>
|
||||
<Text style={styles.cancelButtonText}>Cancel</Text>
|
||||
</TouchableOpacity>
|
||||
|
||||
<TouchableOpacity
|
||||
style={[
|
||||
styles.deleteButton,
|
||||
(confirmText !== "DELETE" || isDeletingRelationship) && styles.deleteButtonDisabled,
|
||||
]}
|
||||
onPress={handleDeleteRelationship}
|
||||
disabled={confirmText !== "DELETE" || isDeletingRelationship}
|
||||
activeOpacity={0.7}
|
||||
>
|
||||
{isDeletingRelationship ? (
|
||||
<ActivityIndicator size="small" color={colors.text} />
|
||||
) : (
|
||||
<>
|
||||
<Ionicons name="trash" size={16} color={colors.text} />
|
||||
<Text style={styles.deleteButtonText}>Delete</Text>
|
||||
</>
|
||||
)}
|
||||
</TouchableOpacity>
|
||||
</View>
|
||||
</View>
|
||||
</KeyboardAvoidingView>
|
||||
</Modal>
|
||||
</>
|
||||
);
|
||||
};
|
||||
@@ -419,15 +406,15 @@ const styles = StyleSheet.create({
|
||||
alignItems: "center",
|
||||
padding: spacing.lg,
|
||||
},
|
||||
modalContent: {
|
||||
width: "100%",
|
||||
maxWidth: 400,
|
||||
padding: spacing.xl,
|
||||
backgroundColor: colors.backgroundLight,
|
||||
borderRadius: radius.xl,
|
||||
borderWidth: 1,
|
||||
borderColor: colors.glass.border,
|
||||
},
|
||||
modalContent: {
|
||||
width: "100%",
|
||||
maxWidth: 400,
|
||||
padding: spacing.xl,
|
||||
backgroundColor: colors.backgroundLight,
|
||||
borderRadius: radius.xl,
|
||||
borderWidth: 1,
|
||||
borderColor: colors.border,
|
||||
},
|
||||
modalHeader: {
|
||||
alignItems: "center",
|
||||
marginBottom: spacing.lg,
|
||||
@@ -492,13 +479,13 @@ const styles = StyleSheet.create({
|
||||
fontWeight: "bold",
|
||||
color: colors.warning,
|
||||
},
|
||||
warningInput: {
|
||||
backgroundColor: colors.glass.background,
|
||||
borderRadius: radius.md,
|
||||
borderWidth: 1,
|
||||
borderColor: colors.warning,
|
||||
padding: spacing.md,
|
||||
...typography.body,
|
||||
warningInput: {
|
||||
backgroundColor: colors.background,
|
||||
borderRadius: radius.md,
|
||||
borderWidth: 1,
|
||||
borderColor: colors.warning,
|
||||
padding: spacing.md,
|
||||
...typography.body,
|
||||
color: colors.text,
|
||||
textAlign: 'center',
|
||||
marginBottom: spacing.xl,
|
||||
@@ -532,13 +519,13 @@ const styles = StyleSheet.create({
|
||||
fontWeight: "bold",
|
||||
color: colors.error,
|
||||
},
|
||||
confirmInput: {
|
||||
backgroundColor: colors.glass.background,
|
||||
borderRadius: radius.md,
|
||||
borderWidth: 1,
|
||||
borderColor: colors.error,
|
||||
padding: spacing.md,
|
||||
...typography.body,
|
||||
confirmInput: {
|
||||
backgroundColor: colors.background,
|
||||
borderRadius: radius.md,
|
||||
borderWidth: 1,
|
||||
borderColor: colors.error,
|
||||
padding: spacing.md,
|
||||
...typography.body,
|
||||
color: colors.text,
|
||||
textAlign: 'center',
|
||||
marginBottom: spacing.xl,
|
||||
@@ -547,15 +534,15 @@ const styles = StyleSheet.create({
|
||||
flexDirection: "row",
|
||||
gap: spacing.md,
|
||||
},
|
||||
cancelButton: {
|
||||
flex: 1,
|
||||
backgroundColor: colors.glass.backgroundLight,
|
||||
paddingVertical: spacing.md,
|
||||
borderRadius: radius.md,
|
||||
alignItems: "center",
|
||||
borderWidth: 1,
|
||||
borderColor: colors.glass.borderLight,
|
||||
},
|
||||
cancelButton: {
|
||||
flex: 1,
|
||||
backgroundColor: colors.background,
|
||||
paddingVertical: spacing.md,
|
||||
borderRadius: radius.md,
|
||||
alignItems: "center",
|
||||
borderWidth: 1,
|
||||
borderColor: colors.border,
|
||||
},
|
||||
cancelButtonText: {
|
||||
...typography.body,
|
||||
color: colors.textSecondary,
|
||||
|
||||
@@ -2,8 +2,7 @@ import React, { useState } from 'react';
|
||||
import { View, Text, StyleSheet, Modal, Platform, KeyboardAvoidingView, TouchableOpacity, ActivityIndicator, TextInput } from 'react-native';
|
||||
import { Ionicons } from '@expo/vector-icons';
|
||||
import Animated, { FadeInDown } from 'react-native-reanimated';
|
||||
import { BlurView } from 'expo-blur';
|
||||
import { GlassCard } from '../../../components/ui';
|
||||
import { GlassCard } from '../../../components/ui';
|
||||
import { colors, spacing, radius, typography } from '../../../theme';
|
||||
|
||||
interface ResetProgressProps {
|
||||
@@ -61,106 +60,100 @@ export const ResetProgress: React.FC<ResetProgressProps> = ({ onResetProgress })
|
||||
</GlassCard>
|
||||
</Animated.View>
|
||||
|
||||
<Modal
|
||||
visible={showResetModal}
|
||||
transparent
|
||||
animationType="fade"
|
||||
onRequestClose={() => !isResetting && setShowResetModal(false)}
|
||||
>
|
||||
<BlurView
|
||||
intensity={Platform.OS === 'ios' ? 20 : 0}
|
||||
tint="dark"
|
||||
style={StyleSheet.absoluteFill}
|
||||
>
|
||||
<KeyboardAvoidingView
|
||||
behavior={Platform.OS === 'ios' ? 'padding' : 'height'}
|
||||
style={styles.modalOverlay}
|
||||
>
|
||||
<View style={styles.modalContent}>
|
||||
<View style={styles.modalHeader}>
|
||||
<View style={styles.modalIconContainer}>
|
||||
<Ionicons name="refresh" size={32} color={colors.warning} />
|
||||
</View>
|
||||
<Text style={styles.modalTitle}>Reset Progress?</Text>
|
||||
</View>
|
||||
|
||||
<Text style={styles.modalDescription}>
|
||||
This action cannot be undone. This will permanently delete:
|
||||
</Text>
|
||||
|
||||
<View style={styles.resetList}>
|
||||
<View style={styles.resetListItem}>
|
||||
<Ionicons name="swap-horizontal" size={16} color={colors.warning} />
|
||||
<Text style={styles.resetListText}>All your swipes</Text>
|
||||
</View>
|
||||
<View style={styles.resetListItem}>
|
||||
<Ionicons name="heart" size={16} color={colors.warning} />
|
||||
<Text style={styles.resetListText}>All matches</Text>
|
||||
</View>
|
||||
<View style={styles.resetListItem}>
|
||||
<Ionicons name="chatbubbles" size={16} color={colors.warning} />
|
||||
<Text style={styles.resetListText}>All chat messages</Text>
|
||||
</View>
|
||||
</View>
|
||||
|
||||
<View style={styles.keepList}>
|
||||
<View style={styles.keepListItem}>
|
||||
<Ionicons name="checkmark-circle" size={16} color={colors.success} />
|
||||
<Text style={styles.keepListText}>Your partner connection stays</Text>
|
||||
</View>
|
||||
</View>
|
||||
|
||||
<Text style={styles.confirmLabel}>
|
||||
Type <Text style={styles.confirmKeyword}>RESET</Text> to confirm:
|
||||
</Text>
|
||||
|
||||
<TextInput
|
||||
style={styles.confirmInput}
|
||||
value={resetConfirmText}
|
||||
onChangeText={setResetConfirmText}
|
||||
placeholder="Type RESET"
|
||||
placeholderTextColor={colors.textTertiary}
|
||||
autoCapitalize="characters"
|
||||
autoCorrect={false}
|
||||
editable={!isResetting}
|
||||
/>
|
||||
|
||||
<View style={styles.modalButtons}>
|
||||
<TouchableOpacity
|
||||
style={styles.cancelButton}
|
||||
onPress={() => {
|
||||
setShowResetModal(false);
|
||||
setResetConfirmText("");
|
||||
}}
|
||||
disabled={isResetting}
|
||||
activeOpacity={0.7}
|
||||
>
|
||||
<Text style={styles.cancelButtonText}>Cancel</Text>
|
||||
</TouchableOpacity>
|
||||
|
||||
<TouchableOpacity
|
||||
style={[
|
||||
styles.confirmResetButton,
|
||||
(resetConfirmText !== "RESET" || isResetting) && styles.confirmResetButtonDisabled,
|
||||
]}
|
||||
onPress={handleReset}
|
||||
disabled={resetConfirmText !== "RESET" || isResetting}
|
||||
activeOpacity={0.7}
|
||||
>
|
||||
{isResetting ? (
|
||||
<ActivityIndicator size="small" color={colors.text} />
|
||||
) : (
|
||||
<>
|
||||
<Ionicons name="refresh" size={16} color={colors.text} />
|
||||
<Text style={styles.confirmResetButtonText}>Reset</Text>
|
||||
</>
|
||||
)}
|
||||
</TouchableOpacity>
|
||||
</View>
|
||||
</View>
|
||||
</KeyboardAvoidingView>
|
||||
</BlurView>
|
||||
</Modal>
|
||||
<Modal
|
||||
visible={showResetModal}
|
||||
transparent
|
||||
animationType="fade"
|
||||
onRequestClose={() => !isResetting && setShowResetModal(false)}
|
||||
>
|
||||
<KeyboardAvoidingView
|
||||
behavior={Platform.OS === 'ios' ? 'padding' : 'height'}
|
||||
style={styles.modalOverlay}
|
||||
>
|
||||
<View style={styles.modalContent}>
|
||||
<View style={styles.modalHeader}>
|
||||
<View style={styles.modalIconContainer}>
|
||||
<Ionicons name="refresh" size={32} color={colors.warning} />
|
||||
</View>
|
||||
<Text style={styles.modalTitle}>Reset Progress?</Text>
|
||||
</View>
|
||||
|
||||
<Text style={styles.modalDescription}>
|
||||
This action cannot be undone. This will permanently delete:
|
||||
</Text>
|
||||
|
||||
<View style={styles.resetList}>
|
||||
<View style={styles.resetListItem}>
|
||||
<Ionicons name="swap-horizontal" size={16} color={colors.warning} />
|
||||
<Text style={styles.resetListText}>All your swipes</Text>
|
||||
</View>
|
||||
<View style={styles.resetListItem}>
|
||||
<Ionicons name="heart" size={16} color={colors.warning} />
|
||||
<Text style={styles.resetListText}>All matches</Text>
|
||||
</View>
|
||||
<View style={styles.resetListItem}>
|
||||
<Ionicons name="chatbubbles" size={16} color={colors.warning} />
|
||||
<Text style={styles.resetListText}>All chat messages</Text>
|
||||
</View>
|
||||
</View>
|
||||
|
||||
<View style={styles.keepList}>
|
||||
<View style={styles.keepListItem}>
|
||||
<Ionicons name="checkmark-circle" size={16} color={colors.success} />
|
||||
<Text style={styles.keepListText}>Your partner connection stays</Text>
|
||||
</View>
|
||||
</View>
|
||||
|
||||
<Text style={styles.confirmLabel}>
|
||||
Type <Text style={styles.confirmKeyword}>RESET</Text> to confirm:
|
||||
</Text>
|
||||
|
||||
<TextInput
|
||||
style={styles.confirmInput}
|
||||
value={resetConfirmText}
|
||||
onChangeText={setResetConfirmText}
|
||||
placeholder="Type RESET"
|
||||
placeholderTextColor={colors.textTertiary}
|
||||
autoCapitalize="characters"
|
||||
autoCorrect={false}
|
||||
editable={!isResetting}
|
||||
/>
|
||||
|
||||
<View style={styles.modalButtons}>
|
||||
<TouchableOpacity
|
||||
style={styles.cancelButton}
|
||||
onPress={() => {
|
||||
setShowResetModal(false);
|
||||
setResetConfirmText("");
|
||||
}}
|
||||
disabled={isResetting}
|
||||
activeOpacity={0.7}
|
||||
>
|
||||
<Text style={styles.cancelButtonText}>Cancel</Text>
|
||||
</TouchableOpacity>
|
||||
|
||||
<TouchableOpacity
|
||||
style={[
|
||||
styles.confirmResetButton,
|
||||
(resetConfirmText !== "RESET" || isResetting) && styles.confirmResetButtonDisabled,
|
||||
]}
|
||||
onPress={handleReset}
|
||||
disabled={resetConfirmText !== "RESET" || isResetting}
|
||||
activeOpacity={0.7}
|
||||
>
|
||||
{isResetting ? (
|
||||
<ActivityIndicator size="small" color={colors.text} />
|
||||
) : (
|
||||
<>
|
||||
<Ionicons name="refresh" size={16} color={colors.text} />
|
||||
<Text style={styles.confirmResetButtonText}>Reset</Text>
|
||||
</>
|
||||
)}
|
||||
</TouchableOpacity>
|
||||
</View>
|
||||
</View>
|
||||
</KeyboardAvoidingView>
|
||||
</Modal>
|
||||
</>
|
||||
);
|
||||
};
|
||||
@@ -233,15 +226,15 @@ const styles = StyleSheet.create({
|
||||
alignItems: "center",
|
||||
padding: spacing.lg,
|
||||
},
|
||||
modalContent: {
|
||||
width: "100%",
|
||||
maxWidth: 400,
|
||||
padding: spacing.xl,
|
||||
backgroundColor: colors.backgroundLight,
|
||||
borderRadius: radius.xl,
|
||||
borderWidth: 1,
|
||||
borderColor: colors.glass.border,
|
||||
},
|
||||
modalContent: {
|
||||
width: "100%",
|
||||
maxWidth: 400,
|
||||
padding: spacing.xl,
|
||||
backgroundColor: colors.backgroundLight,
|
||||
borderRadius: radius.xl,
|
||||
borderWidth: 1,
|
||||
borderColor: colors.border,
|
||||
},
|
||||
modalHeader: {
|
||||
alignItems: "center",
|
||||
marginBottom: spacing.lg,
|
||||
@@ -312,13 +305,13 @@ const styles = StyleSheet.create({
|
||||
fontWeight: "bold",
|
||||
color: colors.warning,
|
||||
},
|
||||
confirmInput: {
|
||||
backgroundColor: colors.glass.background,
|
||||
borderRadius: radius.md,
|
||||
borderWidth: 1,
|
||||
borderColor: colors.warning,
|
||||
padding: spacing.md,
|
||||
...typography.body,
|
||||
confirmInput: {
|
||||
backgroundColor: colors.background,
|
||||
borderRadius: radius.md,
|
||||
borderWidth: 1,
|
||||
borderColor: colors.warning,
|
||||
padding: spacing.md,
|
||||
...typography.body,
|
||||
color: colors.text,
|
||||
textAlign: 'center',
|
||||
marginBottom: spacing.xl,
|
||||
@@ -327,15 +320,15 @@ const styles = StyleSheet.create({
|
||||
flexDirection: "row",
|
||||
gap: spacing.md,
|
||||
},
|
||||
cancelButton: {
|
||||
flex: 1,
|
||||
backgroundColor: colors.glass.backgroundLight,
|
||||
paddingVertical: spacing.md,
|
||||
borderRadius: radius.md,
|
||||
alignItems: "center",
|
||||
borderWidth: 1,
|
||||
borderColor: colors.glass.borderLight,
|
||||
},
|
||||
cancelButton: {
|
||||
flex: 1,
|
||||
backgroundColor: colors.background,
|
||||
paddingVertical: spacing.md,
|
||||
borderRadius: radius.md,
|
||||
alignItems: "center",
|
||||
borderWidth: 1,
|
||||
borderColor: colors.border,
|
||||
},
|
||||
cancelButtonText: {
|
||||
...typography.body,
|
||||
color: colors.textSecondary,
|
||||
|
||||
@@ -53,14 +53,16 @@ const styles = StyleSheet.create({
|
||||
borderBottomWidth: 1,
|
||||
borderBottomColor: `${featureColors.profile.accent}15`,
|
||||
},
|
||||
backButton: {
|
||||
width: 40,
|
||||
height: 40,
|
||||
borderRadius: 20,
|
||||
backgroundColor: colors.glass.background,
|
||||
justifyContent: 'center',
|
||||
alignItems: 'center',
|
||||
},
|
||||
backButton: {
|
||||
width: 40,
|
||||
height: 40,
|
||||
borderRadius: 20,
|
||||
backgroundColor: colors.backgroundLight,
|
||||
borderWidth: 1,
|
||||
borderColor: colors.border,
|
||||
justifyContent: 'center',
|
||||
alignItems: 'center',
|
||||
},
|
||||
title: {
|
||||
...typography.headline,
|
||||
color: colors.text,
|
||||
|
||||
@@ -14,7 +14,7 @@ export interface SettingsSectionProps {
|
||||
}
|
||||
|
||||
/**
|
||||
* Reusable settings section with title and glass card container.
|
||||
* Reusable settings section with title and solid card container.
|
||||
*/
|
||||
export function SettingsSection({ title, delay = 200, children }: SettingsSectionProps) {
|
||||
return (
|
||||
|
||||
@@ -242,24 +242,57 @@ const styles = StyleSheet.create({
|
||||
...StyleSheet.absoluteFillObject,
|
||||
opacity: 0.3,
|
||||
},
|
||||
proCardInner: {
|
||||
backgroundColor: 'rgba(22, 33, 62, 0.8)',
|
||||
margin: 1,
|
||||
borderRadius: radius.lg - 1,
|
||||
padding: spacing.md,
|
||||
},
|
||||
proCardInner: {
|
||||
backgroundColor: colors.backgroundLight, // Solid/Flat background
|
||||
margin: 1,
|
||||
borderRadius: radius.lg - 1,
|
||||
padding: spacing.md,
|
||||
},
|
||||
// ...
|
||||
proBadge: {
|
||||
width: 36,
|
||||
height: 36,
|
||||
borderRadius: 18,
|
||||
justifyContent: 'center',
|
||||
alignItems: 'center',
|
||||
// Removed glow
|
||||
},
|
||||
// ...
|
||||
manageButton: {
|
||||
paddingHorizontal: spacing.md,
|
||||
paddingVertical: spacing.sm,
|
||||
borderRadius: radius.md,
|
||||
backgroundColor: colors.background,
|
||||
borderWidth: 1,
|
||||
borderColor: colors.border,
|
||||
},
|
||||
// ...
|
||||
proStatusRow: {
|
||||
flexDirection: 'row',
|
||||
marginTop: spacing.md,
|
||||
paddingTop: spacing.md,
|
||||
borderTopWidth: 1,
|
||||
borderTopColor: colors.border,
|
||||
gap: spacing.lg,
|
||||
},
|
||||
// ...
|
||||
freeCardInner: {
|
||||
backgroundColor: colors.backgroundLight, // Solid/Flat background
|
||||
margin: 1,
|
||||
borderRadius: radius.lg - 1,
|
||||
padding: spacing.lg,
|
||||
},
|
||||
// ...
|
||||
ctaButtonContainer: {
|
||||
borderRadius: radius.md,
|
||||
overflow: 'hidden',
|
||||
// Removed glow
|
||||
},
|
||||
proHeader: {
|
||||
flexDirection: 'row',
|
||||
alignItems: 'center',
|
||||
},
|
||||
proBadge: {
|
||||
width: 36,
|
||||
height: 36,
|
||||
borderRadius: 18,
|
||||
justifyContent: 'center',
|
||||
alignItems: 'center',
|
||||
...shadows.glow(colors.premium.gold),
|
||||
},
|
||||
|
||||
proHeaderText: {
|
||||
flex: 1,
|
||||
marginLeft: spacing.md,
|
||||
@@ -274,27 +307,13 @@ const styles = StyleSheet.create({
|
||||
color: colors.textTertiary,
|
||||
marginTop: 2,
|
||||
},
|
||||
manageButton: {
|
||||
paddingHorizontal: spacing.md,
|
||||
paddingVertical: spacing.sm,
|
||||
borderRadius: radius.md,
|
||||
backgroundColor: colors.glass.background,
|
||||
borderWidth: 1,
|
||||
borderColor: `${colors.premium.gold}40`,
|
||||
},
|
||||
|
||||
manageButtonText: {
|
||||
...typography.subhead,
|
||||
color: colors.premium.gold,
|
||||
fontWeight: '600',
|
||||
},
|
||||
proStatusRow: {
|
||||
flexDirection: 'row',
|
||||
marginTop: spacing.md,
|
||||
paddingTop: spacing.md,
|
||||
borderTopWidth: 1,
|
||||
borderTopColor: colors.glass.border,
|
||||
gap: spacing.lg,
|
||||
},
|
||||
|
||||
proStatusItem: {
|
||||
flexDirection: 'row',
|
||||
alignItems: 'center',
|
||||
@@ -313,12 +332,7 @@ const styles = StyleSheet.create({
|
||||
freeBorderGradient: {
|
||||
...StyleSheet.absoluteFillObject,
|
||||
},
|
||||
freeCardInner: {
|
||||
backgroundColor: 'rgba(22, 33, 62, 0.85)',
|
||||
margin: 1,
|
||||
borderRadius: radius.lg - 1,
|
||||
padding: spacing.lg,
|
||||
},
|
||||
|
||||
freeHeader: {
|
||||
alignItems: 'center',
|
||||
marginBottom: spacing.lg,
|
||||
@@ -358,11 +372,7 @@ const styles = StyleSheet.create({
|
||||
...typography.body,
|
||||
color: colors.text,
|
||||
},
|
||||
ctaButtonContainer: {
|
||||
borderRadius: radius.md,
|
||||
overflow: 'hidden',
|
||||
...shadows.glow(colors.premium.gold),
|
||||
},
|
||||
|
||||
ctaButton: {
|
||||
flexDirection: 'row',
|
||||
alignItems: 'center',
|
||||
|
||||
@@ -16,7 +16,7 @@ import { Ionicons } from '@expo/vector-icons';
|
||||
import { LinearGradient } from 'expo-linear-gradient';
|
||||
import Animated, { FadeInDown } from 'react-native-reanimated';
|
||||
|
||||
import { GradientBackground, GlassCard, GlassButton } from '../../../components/ui';
|
||||
import { GradientBackground } from '../../../components/ui';
|
||||
import { colors, featureColors, spacing, typography, radius } from '../../../theme';
|
||||
import { useAuthStore } from '../../../store';
|
||||
import { supabase } from '../../../lib/supabase';
|
||||
@@ -53,7 +53,7 @@ const GENDER_OPTIONS: { value: Gender; label: string; icon: string }[] = [
|
||||
* Profile settings sub-screen for editing avatar, display name, and gender.
|
||||
*/
|
||||
export function ProfileSettingsScreen() {
|
||||
const { user, fetchUser } = useAuthStore();
|
||||
const { user, fetchUser, isAnonymous } = useAuthStore();
|
||||
const settings = useProfileSettings();
|
||||
const authEmail = useAuthEmail();
|
||||
|
||||
@@ -260,10 +260,12 @@ export function ProfileSettingsScreen() {
|
||||
color={colors.textTertiary}
|
||||
style={styles.inputIcon}
|
||||
/>
|
||||
<Text style={styles.readOnlyText}>{authEmail || 'Loading...'}</Text>
|
||||
<Text style={styles.readOnlyText}>
|
||||
{authEmail || (isAnonymous ? 'Guest account' : 'Loading...')}
|
||||
</Text>
|
||||
</View>
|
||||
<Text style={styles.inputHint}>
|
||||
Email cannot be changed
|
||||
{isAnonymous ? 'Save your account to add an email' : 'Email cannot be changed'}
|
||||
</Text>
|
||||
</SettingsSection>
|
||||
|
||||
@@ -300,11 +302,41 @@ const styles = StyleSheet.create({
|
||||
padding: 4,
|
||||
justifyContent: 'center',
|
||||
alignItems: 'center',
|
||||
shadowColor: featureColors.profile.accent,
|
||||
shadowOffset: { width: 0, height: 4 },
|
||||
shadowOpacity: 0.3,
|
||||
shadowRadius: 8,
|
||||
elevation: 6,
|
||||
// Removed shadows
|
||||
},
|
||||
// ...
|
||||
inputContainer: {
|
||||
flexDirection: 'row',
|
||||
alignItems: 'center',
|
||||
backgroundColor: colors.background, // Flat
|
||||
borderRadius: radius.md,
|
||||
borderWidth: 1,
|
||||
borderColor: colors.border,
|
||||
paddingHorizontal: spacing.md,
|
||||
},
|
||||
// ...
|
||||
readOnlyField: {
|
||||
flexDirection: 'row',
|
||||
alignItems: 'center',
|
||||
backgroundColor: colors.backgroundLight,
|
||||
borderRadius: radius.md,
|
||||
borderWidth: 1,
|
||||
borderColor: colors.border,
|
||||
paddingHorizontal: spacing.md,
|
||||
paddingVertical: spacing.md,
|
||||
opacity: 0.7,
|
||||
},
|
||||
// ...
|
||||
genderOption: {
|
||||
width: '48%',
|
||||
alignItems: 'center',
|
||||
backgroundColor: colors.backgroundLight,
|
||||
borderRadius: radius.lg,
|
||||
borderWidth: 2,
|
||||
borderColor: colors.border,
|
||||
paddingVertical: spacing.md,
|
||||
paddingHorizontal: spacing.sm,
|
||||
gap: spacing.xs,
|
||||
},
|
||||
avatarImage: {
|
||||
width: '100%',
|
||||
@@ -354,15 +386,7 @@ const styles = StyleSheet.create({
|
||||
color: colors.textTertiary,
|
||||
},
|
||||
// Input
|
||||
inputContainer: {
|
||||
flexDirection: 'row',
|
||||
alignItems: 'center',
|
||||
backgroundColor: colors.glass.background,
|
||||
borderRadius: radius.md,
|
||||
borderWidth: 1,
|
||||
borderColor: colors.glass.border,
|
||||
paddingHorizontal: spacing.md,
|
||||
},
|
||||
|
||||
inputIcon: {
|
||||
marginRight: spacing.sm,
|
||||
},
|
||||
@@ -379,17 +403,7 @@ const styles = StyleSheet.create({
|
||||
marginLeft: spacing.xs,
|
||||
},
|
||||
// Read-only field
|
||||
readOnlyField: {
|
||||
flexDirection: 'row',
|
||||
alignItems: 'center',
|
||||
backgroundColor: colors.glass.background,
|
||||
borderRadius: radius.md,
|
||||
borderWidth: 1,
|
||||
borderColor: colors.glass.border,
|
||||
paddingHorizontal: spacing.md,
|
||||
paddingVertical: spacing.md,
|
||||
opacity: 0.7,
|
||||
},
|
||||
|
||||
readOnlyText: {
|
||||
...typography.body,
|
||||
color: colors.textSecondary,
|
||||
@@ -405,17 +419,7 @@ const styles = StyleSheet.create({
|
||||
flexWrap: 'wrap',
|
||||
gap: spacing.sm,
|
||||
},
|
||||
genderOption: {
|
||||
width: '48%',
|
||||
alignItems: 'center',
|
||||
backgroundColor: colors.glass.background,
|
||||
borderRadius: radius.lg,
|
||||
borderWidth: 2,
|
||||
borderColor: colors.glass.border,
|
||||
paddingVertical: spacing.md,
|
||||
paddingHorizontal: spacing.sm,
|
||||
gap: spacing.xs,
|
||||
},
|
||||
|
||||
genderOptionSelected: {
|
||||
borderColor: colors.primary,
|
||||
backgroundColor: colors.primaryLight,
|
||||
|
||||
@@ -510,7 +510,7 @@ const styles = StyleSheet.create({
|
||||
},
|
||||
modeToggleContainer: {
|
||||
flexDirection: "row",
|
||||
backgroundColor: colors.glass.background,
|
||||
backgroundColor: colors.backgroundLight,
|
||||
borderRadius: radius.md,
|
||||
padding: 4,
|
||||
marginBottom: spacing.md,
|
||||
|
||||
@@ -58,11 +58,11 @@ const styles = StyleSheet.create({
|
||||
paddingTop: spacing.lg,
|
||||
paddingBottom: Platform.OS === 'ios' ? 40 : 20,
|
||||
},
|
||||
divider: {
|
||||
height: 1,
|
||||
backgroundColor: colors.glass.border,
|
||||
marginVertical: spacing.md,
|
||||
},
|
||||
});
|
||||
divider: {
|
||||
height: 1,
|
||||
backgroundColor: colors.border,
|
||||
marginVertical: spacing.md,
|
||||
},
|
||||
});
|
||||
|
||||
export default SupportScreen;
|
||||
Reference in new issue
Block a user