mirror of
https://github.com/jjbrunton/Sauci.git
synced 2026-10-31 18:19:43 +00:00
Update
This commit is contained in:
1 parent
e7379ccdb1
commit
d18f9d32b0
49 files changed
+3739
-665
No files matched your search
@@ -1,6 +1,6 @@
|
||||
import { Tabs, useRouter, useSegments } from "expo-router";
|
||||
import { Ionicons } from "@expo/vector-icons";
|
||||
import { View, ActivityIndicator, StyleSheet, Platform, Modal, Text, Pressable, Animated, AppState, AppStateStatus } from "react-native";
|
||||
import { View, ActivityIndicator, StyleSheet, Platform, Text, Pressable, Animated, AppState, AppStateStatus } from "react-native";
|
||||
import { BlurView } from "expo-blur";
|
||||
import { LinearGradient } from "expo-linear-gradient";
|
||||
import { useEffect, useState, useCallback, useRef } from "react";
|
||||
@@ -17,6 +17,8 @@ import type { Database } from "../../src/types/supabase";
|
||||
type Message = Database["public"]["Tables"]["messages"]["Row"];
|
||||
|
||||
function TabBarBackground() {
|
||||
// Only use BlurView on iOS - Android experimental blur causes white overlay artifacts
|
||||
// The solid rgba background in tabBarStyle provides the visual effect on Android
|
||||
if (Platform.OS === 'ios') {
|
||||
return (
|
||||
<BlurView
|
||||
@@ -26,15 +28,7 @@ function TabBarBackground() {
|
||||
/>
|
||||
);
|
||||
}
|
||||
// Android fallback - use experimental blur method
|
||||
return (
|
||||
<BlurView
|
||||
intensity={80}
|
||||
tint="dark"
|
||||
style={StyleSheet.absoluteFill}
|
||||
experimentalBlurMethod="dimezisBlurView"
|
||||
/>
|
||||
);
|
||||
return null;
|
||||
}
|
||||
|
||||
interface PlayTabButtonProps {
|
||||
@@ -82,6 +76,7 @@ export default function AppLayout() {
|
||||
const { fetchEnabledPacks } = usePacksStore();
|
||||
const { initializeRevenueCat } = useSubscriptionStore();
|
||||
const [matchNotification, setMatchNotification] = useState<MatchWithQuestion | null>(null);
|
||||
const matchModalAnim = useRef(new Animated.Value(0)).current;
|
||||
const messageToastAnim = useRef(new Animated.Value(-100)).current;
|
||||
const messageTimeoutRef = useRef<NodeJS.Timeout | null>(null);
|
||||
const [isLocked, setIsLocked] = useState(false);
|
||||
@@ -384,9 +379,27 @@ export default function AppLayout() {
|
||||
};
|
||||
}, [lastMessage]);
|
||||
|
||||
// Animate match modal when notification changes
|
||||
useEffect(() => {
|
||||
if (matchNotification) {
|
||||
Animated.spring(matchModalAnim, {
|
||||
toValue: 1,
|
||||
useNativeDriver: true,
|
||||
tension: 80,
|
||||
friction: 10,
|
||||
}).start();
|
||||
}
|
||||
}, [matchNotification, matchModalAnim]);
|
||||
|
||||
const dismissNotification = useCallback(() => {
|
||||
setMatchNotification(null);
|
||||
}, []);
|
||||
Animated.timing(matchModalAnim, {
|
||||
toValue: 0,
|
||||
duration: 200,
|
||||
useNativeDriver: true,
|
||||
}).start(() => {
|
||||
setMatchNotification(null);
|
||||
});
|
||||
}, [matchModalAnim]);
|
||||
|
||||
const dismissMessageToast = useCallback(() => {
|
||||
Animated.timing(messageToastAnim, {
|
||||
@@ -507,15 +520,32 @@ export default function AppLayout() {
|
||||
/>
|
||||
</Tabs>
|
||||
|
||||
{/* Match Notification Modal */}
|
||||
<Modal
|
||||
visible={!!matchNotification}
|
||||
transparent
|
||||
animationType="fade"
|
||||
onRequestClose={dismissNotification}
|
||||
>
|
||||
<Pressable style={styles.modalOverlay} onPress={dismissNotification}>
|
||||
<View style={styles.modalContent}>
|
||||
{/* Match Notification Overlay - Using Animated.View instead of Modal for Android compatibility */}
|
||||
{matchNotification && (
|
||||
<Animated.View
|
||||
style={[
|
||||
styles.matchOverlay,
|
||||
{
|
||||
opacity: matchModalAnim,
|
||||
},
|
||||
]}
|
||||
>
|
||||
<Pressable style={styles.matchOverlayBackground} onPress={dismissNotification} />
|
||||
<Animated.View
|
||||
style={[
|
||||
styles.modalContent,
|
||||
{
|
||||
transform: [
|
||||
{
|
||||
scale: matchModalAnim.interpolate({
|
||||
inputRange: [0, 1],
|
||||
outputRange: [0.8, 1],
|
||||
}),
|
||||
},
|
||||
],
|
||||
},
|
||||
]}
|
||||
>
|
||||
<View style={styles.modalHeader}>
|
||||
<Ionicons name="heart" size={48} color={colors.primary} />
|
||||
<Text style={styles.modalTitle}>It's a Match!</Text>
|
||||
@@ -534,9 +564,9 @@ export default function AppLayout() {
|
||||
<Pressable style={styles.dismissButton} onPress={dismissNotification}>
|
||||
<Text style={styles.dismissButtonText}>Nice!</Text>
|
||||
</Pressable>
|
||||
</View>
|
||||
</Pressable>
|
||||
</Modal>
|
||||
</Animated.View>
|
||||
</Animated.View>
|
||||
)}
|
||||
|
||||
{/* Message Toast Notification */}
|
||||
{lastMessage && (
|
||||
@@ -581,19 +611,23 @@ const styles = StyleSheet.create({
|
||||
alignItems: "center",
|
||||
backgroundColor: colors.background,
|
||||
},
|
||||
modalOverlay: {
|
||||
flex: 1,
|
||||
backgroundColor: 'rgba(0, 0, 0, 0.7)',
|
||||
matchOverlay: {
|
||||
...StyleSheet.absoluteFillObject,
|
||||
justifyContent: 'center',
|
||||
alignItems: 'center',
|
||||
padding: spacing.lg,
|
||||
zIndex: 9999,
|
||||
elevation: 9999,
|
||||
},
|
||||
matchOverlayBackground: {
|
||||
...StyleSheet.absoluteFillObject,
|
||||
backgroundColor: 'rgba(0, 0, 0, 0.7)',
|
||||
},
|
||||
modalContent: {
|
||||
backgroundColor: colors.backgroundLight,
|
||||
borderRadius: radius.xl,
|
||||
padding: spacing.xl,
|
||||
alignItems: 'center',
|
||||
width: '100%',
|
||||
width: '90%',
|
||||
maxWidth: 340,
|
||||
borderWidth: 1,
|
||||
borderColor: colors.glass.border,
|
||||
|
||||
@@ -8,6 +8,7 @@ import { supabase } from "../../src/lib/supabase";
|
||||
import { usePacksStore, useAuthStore } from "../../src/store";
|
||||
import { skipQuestion, getSkippedQuestionIds } from "../../src/lib/skippedQuestions";
|
||||
import { hasSeenSwipeTutorial, markSwipeTutorialSeen } from "../../src/lib/swipeTutorialSeen";
|
||||
import { invokeWithAuthRetry } from "../../src/lib/authErrorHandler";
|
||||
import SwipeCard from "../../src/components/SwipeCard";
|
||||
import SwipeTutorial from "../../src/components/SwipeTutorial";
|
||||
import { GradientBackground, GlassCard, GlassButton } from "../../src/components/ui";
|
||||
@@ -120,21 +121,11 @@ export default function SwipeScreen() {
|
||||
const answer = direction === "right" ? "yes" : direction === "left" ? "no" : "maybe";
|
||||
|
||||
try {
|
||||
const { data: { session } } = await supabase.auth.getSession();
|
||||
|
||||
if (!session?.access_token) {
|
||||
console.error("No valid session - user may need to re-login");
|
||||
return;
|
||||
}
|
||||
|
||||
const { error } = await supabase.functions.invoke("submit-response", {
|
||||
const { error } = await invokeWithAuthRetry("submit-response", {
|
||||
body: {
|
||||
question_id: question.id,
|
||||
answer,
|
||||
},
|
||||
headers: {
|
||||
Authorization: `Bearer ${session.access_token}`,
|
||||
},
|
||||
});
|
||||
|
||||
if (error) {
|
||||
|
||||
Reference in new issue
Block a user