Collapse match card header on keyboard open

This commit is contained in:
jjbrunton committed 2026-01-04 19:55:32 +00:00
1 parent 58209b94ef
commit 474bbb6829
2 files changed
+141 -99

No files matched your search

@@ -505,7 +505,10 @@
LIBRARY_SEARCH_PATHS = "$(SDKROOT)/usr/lib/swift\"$(inherited)\""; LIBRARY_SEARCH_PATHS = "$(SDKROOT)/usr/lib/swift\"$(inherited)\"";
MTL_ENABLE_DEBUG_INFO = YES; MTL_ENABLE_DEBUG_INFO = YES;
ONLY_ACTIVE_ARCH = YES; ONLY_ACTIVE_ARCH = YES;
OTHER_LDFLAGS = "$(inherited) "; OTHER_LDFLAGS = (
"$(inherited)",
" ",
);
REACT_NATIVE_PATH = "${PODS_ROOT}/../../../../node_modules/react-native"; REACT_NATIVE_PATH = "${PODS_ROOT}/../../../../node_modules/react-native";
SDKROOT = iphoneos; SDKROOT = iphoneos;
SWIFT_ACTIVE_COMPILATION_CONDITIONS = "$(inherited) DEBUG"; SWIFT_ACTIVE_COMPILATION_CONDITIONS = "$(inherited) DEBUG";
@@ -560,7 +563,10 @@
); );
LIBRARY_SEARCH_PATHS = "$(SDKROOT)/usr/lib/swift\"$(inherited)\""; LIBRARY_SEARCH_PATHS = "$(SDKROOT)/usr/lib/swift\"$(inherited)\"";
MTL_ENABLE_DEBUG_INFO = NO; MTL_ENABLE_DEBUG_INFO = NO;
OTHER_LDFLAGS = "$(inherited) "; OTHER_LDFLAGS = (
"$(inherited)",
" ",
);
REACT_NATIVE_PATH = "${PODS_ROOT}/../../../../node_modules/react-native"; REACT_NATIVE_PATH = "${PODS_ROOT}/../../../../node_modules/react-native";
SDKROOT = iphoneos; SDKROOT = iphoneos;
USE_HERMES = true; USE_HERMES = true;
@@ -3,7 +3,14 @@ import { View, Text, StyleSheet, TouchableOpacity } from 'react-native';
import { Image } from 'expo-image'; import { Image } from 'expo-image';
import { Ionicons } from '@expo/vector-icons'; import { Ionicons } from '@expo/vector-icons';
import { LinearGradient } from 'expo-linear-gradient'; import { LinearGradient } from 'expo-linear-gradient';
import Animated, { FadeIn, FadeInDown } from 'react-native-reanimated'; import Animated, {
FadeIn,
FadeInDown,
useAnimatedKeyboard,
useAnimatedStyle,
interpolate,
Extrapolation
} from 'react-native-reanimated';
import { EdgeInsets } from 'react-native-safe-area-context'; import { EdgeInsets } from 'react-native-safe-area-context';
import { DecorativeSeparator } from '../../../components/ui'; import { DecorativeSeparator } from '../../../components/ui';
@@ -30,6 +37,29 @@ export const ChatHeader: React.FC<ChatHeaderProps> = ({
onBack, onBack,
}) => { }) => {
const isYesYes = match?.match_type === 'yes_yes'; const isYesYes = match?.match_type === 'yes_yes';
// Modern reanimated keyboard hook
const keyboard = useAnimatedKeyboard();
const containerAnimatedStyle = useAnimatedStyle(() => {
// Collapse when keyboard height exceeds 50px
const animationValue = interpolate(
keyboard.height.value,
[0, 60],
[0, 1],
Extrapolation.CLAMP
);
return {
opacity: interpolate(animationValue, [0, 1], [1, 0]),
maxHeight: interpolate(animationValue, [0, 1], [500, 0]), // Assuming card max height
marginTop: interpolate(animationValue, [0, 1], [spacing.sm, 0]),
transform: [
{ scale: interpolate(animationValue, [0, 1], [1, 0.95]) }
],
overflow: 'hidden',
};
});
return ( return (
<> <>
@@ -102,106 +132,108 @@ export const ChatHeader: React.FC<ChatHeaderProps> = ({
{match?.question && ( {match?.question && (
<Animated.View <Animated.View
entering={FadeInDown.delay(100).duration(400).springify()} entering={FadeInDown.delay(100).duration(400).springify()}
style={styles.matchCard} style={[styles.matchCardContainer, containerAnimatedStyle]}
> >
{/* Card gradient background */} <View style={styles.matchCard}>
<LinearGradient {/* Card gradient background */}
colors={isYesYes
? ['rgba(212, 175, 55, 0.12)', 'rgba(184, 134, 11, 0.08)']
: ['rgba(232, 164, 174, 0.1)', 'rgba(22, 33, 62, 0.6)']
}
style={StyleSheet.absoluteFill}
start={{ x: 0, y: 0 }}
end={{ x: 1, y: 1 }}
/>
{/* Top glow for YES+YES */}
{isYesYes && (
<LinearGradient <LinearGradient
colors={[`${ACCENT_RGBA}0.15)`, 'transparent']} colors={isYesYes
style={styles.matchCardGlow} ? ['rgba(212, 175, 55, 0.12)', 'rgba(184, 134, 11, 0.08)']
start={{ x: 0.5, y: 0 }} : ['rgba(232, 164, 174, 0.1)', 'rgba(22, 33, 62, 0.6)']
end={{ x: 0.5, y: 1 }} }
style={StyleSheet.absoluteFill}
start={{ x: 0, y: 0 }}
end={{ x: 1, y: 1 }}
/> />
)}
{/* Match Label */} {/* Top glow for YES+YES */}
<Text style={[styles.matchLabel, isYesYes && styles.matchLabelYesYes]}> {isYesYes && (
{isYesYes ? "PERFECT MATCH" : "SOFT MATCH"} <LinearGradient
</Text> colors={[`${ACCENT_RGBA}0.15)`, 'transparent']}
style={styles.matchCardGlow}
start={{ x: 0.5, y: 0 }}
end={{ x: 0.5, y: 1 }}
/>
)}
{/* Decorative Separator */} {/* Match Label */}
<DecorativeSeparator <Text style={[styles.matchLabel, isYesYes && styles.matchLabelYesYes]}>
variant={isYesYes ? "gold" : "rose"} {isYesYes ? "PERFECT MATCH" : "SOFT MATCH"}
width="60%"
marginVertical={spacing.md}
/>
{/* Question and Responses - Show who answered what */}
{match.responses && match.responses.length > 0 ? (
<View style={styles.responsesContainer}>
{(() => {
// Sort responses by created_at to determine who answered first
const sortedResponses = [...match.responses].sort(
(a: any, b: any) => new Date(a.created_at).getTime() - new Date(b.created_at).getTime()
);
const isTwoPart = !!match.question.partner_text;
return sortedResponses.map((response: any, index: number) => {
const isUser = response.user_id === user?.id;
const isYes = response.answer === 'yes';
const name = isUser ? 'You' : (response.profiles?.name?.split(' ')[0] || 'Partner');
// First responder sees main text, second sees partner_text
const questionText = isTwoPart && index === 1
? match.question.partner_text
: match.question.text;
return (
<View key={response.user_id} style={styles.responseCard}>
{/* Name and Answer Badge */}
<View style={styles.responseHeader}>
<Text style={styles.responseName}>{name}</Text>
{isYes ? (
<LinearGradient
colors={isYesYes
? gradients.premiumGold as [string, string]
: [colors.success, '#27ae60']
}
style={styles.answerBadge}
start={{ x: 0, y: 0 }}
end={{ x: 1, y: 1 }}
>
<Ionicons name="checkmark" size={10} color={colors.text} />
<Text style={styles.answerBadgeText}>YES</Text>
</LinearGradient>
) : (
<View style={styles.answerBadgeMaybe}>
<Ionicons name="help" size={10} color={colors.warning} />
<Text style={styles.answerBadgeTextMaybe}>MAYBE</Text>
</View>
)}
</View>
{/* Question they saw */}
<Text style={styles.responseQuestionText}>
"{questionText}"
</Text>
</View>
);
});
})()}
</View>
) : (
<Text style={styles.matchQuestionText}>
"{match.question.text}"
</Text> </Text>
)}
{/* Premium border */} {/* Decorative Separator */}
<View style={[ <DecorativeSeparator
styles.matchCardBorder, variant={isYesYes ? "gold" : "rose"}
isYesYes && styles.matchCardBorderYesYes width="60%"
]} pointerEvents="none" /> marginVertical={spacing.md}
/>
{/* Question and Responses - Show who answered what */}
{match.responses && match.responses.length > 0 ? (
<View style={styles.responsesContainer}>
{(() => {
// Sort responses by created_at to determine who answered first
const sortedResponses = [...match.responses].sort(
(a: any, b: any) => new Date(a.created_at).getTime() - new Date(b.created_at).getTime()
);
const isTwoPart = !!match.question.partner_text;
return sortedResponses.map((response: any, index: number) => {
const isUser = response.user_id === user?.id;
const isYes = response.answer === 'yes';
const name = isUser ? 'You' : (response.profiles?.name?.split(' ')[0] || 'Partner');
// First responder sees main text, second sees partner_text
const questionText = isTwoPart && index === 1
? match.question.partner_text
: match.question.text;
return (
<View key={response.user_id} style={styles.responseCard}>
{/* Name and Answer Badge */}
<View style={styles.responseHeader}>
<Text style={styles.responseName}>{name}</Text>
{isYes ? (
<LinearGradient
colors={isYesYes
? gradients.premiumGold as [string, string]
: [colors.success, '#27ae60']
}
style={styles.answerBadge}
start={{ x: 0, y: 0 }}
end={{ x: 1, y: 1 }}
>
<Ionicons name="checkmark" size={10} color={colors.text} />
<Text style={styles.answerBadgeText}>YES</Text>
</LinearGradient>
) : (
<View style={styles.answerBadgeMaybe}>
<Ionicons name="help" size={10} color={colors.warning} />
<Text style={styles.answerBadgeTextMaybe}>MAYBE</Text>
</View>
)}
</View>
{/* Question they saw */}
<Text style={styles.responseQuestionText}>
"{questionText}"
</Text>
</View>
);
});
})()}
</View>
) : (
<Text style={styles.matchQuestionText}>
"{match.question.text}"
</Text>
)}
{/* Premium border */}
<View style={[
styles.matchCardBorder,
isYesYes && styles.matchCardBorderYesYes
]} pointerEvents="none" />
</View>
</Animated.View> </Animated.View>
)} )}
</> </>
@@ -301,10 +333,14 @@ const styles = StyleSheet.create({
height: 1, height: 1,
backgroundColor: `${ACCENT_RGBA}0.15)`, backgroundColor: `${ACCENT_RGBA}0.15)`,
}, },
matchCard: { matchCardContainer: {
marginHorizontal: spacing.md, marginHorizontal: spacing.md,
marginTop: spacing.sm, // Vertical margins are handled by animation
marginBottom: spacing.xs, borderRadius: radius.lg,
overflow: 'hidden',
},
matchCard: {
// Margins moved to container
borderRadius: radius.lg, borderRadius: radius.lg,
paddingVertical: spacing.md, paddingVertical: spacing.md,
paddingHorizontal: spacing.md, paddingHorizontal: spacing.md,