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
d18f9d32b0
commit
f7eaf0adcf
24 files changed
+1826
-393
No files matched your search
@@ -1,5 +1,6 @@
|
||||
import { useState, useEffect, useRef, useCallback } from "react";
|
||||
import { View, Text, StyleSheet, FlatList, TextInput, TouchableOpacity, KeyboardAvoidingView, Platform, Image, ActivityIndicator, Alert, Modal, Dimensions } from "react-native";
|
||||
import { View, Text, StyleSheet, FlatList, TextInput, TouchableOpacity, KeyboardAvoidingView, Platform, ActivityIndicator, Alert, Modal, Dimensions } from "react-native";
|
||||
import { Image } from "expo-image";
|
||||
import { useLocalSearchParams, useRouter } from "expo-router";
|
||||
import { useFocusEffect } from "@react-navigation/native";
|
||||
import { useSafeAreaInsets } from "react-native-safe-area-context";
|
||||
@@ -15,6 +16,7 @@ import { Database } from "../../../src/types/supabase";
|
||||
import { GradientBackground } from "../../../src/components/ui";
|
||||
import { colors, gradients, spacing, radius, typography } from "../../../src/theme";
|
||||
import { Events } from "../../../src/lib/analytics";
|
||||
import { getCachedSignedUrl, getStoragePath } from "../../../src/lib/imageCache";
|
||||
|
||||
type Message = Database["public"]["Tables"]["messages"]["Row"];
|
||||
|
||||
@@ -356,6 +358,8 @@ export default function ChatScreen() {
|
||||
<Image
|
||||
source={{ uri: partner.avatar_url }}
|
||||
style={styles.headerAvatar}
|
||||
cachePolicy="disk"
|
||||
transition={200}
|
||||
/>
|
||||
) : (
|
||||
<LinearGradient
|
||||
@@ -492,7 +496,8 @@ export default function ChatScreen() {
|
||||
<Image
|
||||
source={{ uri: fullScreenImage }}
|
||||
style={styles.fullScreenImage}
|
||||
resizeMode="contain"
|
||||
contentFit="contain"
|
||||
cachePolicy="disk"
|
||||
onLoadStart={() => setFullScreenImageLoading(true)}
|
||||
onLoadEnd={() => setFullScreenImageLoading(false)}
|
||||
/>
|
||||
@@ -503,16 +508,6 @@ export default function ChatScreen() {
|
||||
);
|
||||
}
|
||||
|
||||
// Extract storage path from media_path (handles both old full URLs and new path-only format)
|
||||
function getStoragePath(mediaPath: string): string {
|
||||
if (mediaPath.startsWith('http')) {
|
||||
// Old format: extract path from full URL
|
||||
const match = mediaPath.match(/\/chat-media\/(.+)$/);
|
||||
return match ? match[1] : mediaPath;
|
||||
}
|
||||
return mediaPath;
|
||||
}
|
||||
|
||||
function MessageContent({
|
||||
item,
|
||||
isMe,
|
||||
@@ -530,26 +525,26 @@ function MessageContent({
|
||||
useEffect(() => {
|
||||
if (!item.media_path) return;
|
||||
|
||||
let isMounted = true;
|
||||
|
||||
const fetchSignedUrl = async () => {
|
||||
const storagePath = getStoragePath(item.media_path!);
|
||||
const { data, error } = await supabase.storage
|
||||
.from('chat-media')
|
||||
.createSignedUrl(storagePath, 3600); // 1 hour expiry
|
||||
const url = await getCachedSignedUrl(storagePath);
|
||||
|
||||
if (error) {
|
||||
console.error('Failed to get signed URL:', error);
|
||||
setUrlError(true);
|
||||
return;
|
||||
}
|
||||
if (!isMounted) return;
|
||||
|
||||
if (data?.signedUrl) {
|
||||
setSignedUrl(data.signedUrl);
|
||||
if (url) {
|
||||
setSignedUrl(url);
|
||||
} else {
|
||||
setUrlError(true);
|
||||
}
|
||||
};
|
||||
|
||||
fetchSignedUrl();
|
||||
|
||||
return () => {
|
||||
isMounted = false;
|
||||
};
|
||||
}, [item.media_path]);
|
||||
|
||||
if (item.media_path) {
|
||||
@@ -568,6 +563,8 @@ function MessageContent({
|
||||
source={{ uri: signedUrl }}
|
||||
style={styles.messageImage}
|
||||
blurRadius={(!isMe && !isViewed) ? 20 : 0}
|
||||
cachePolicy="disk"
|
||||
transition={200}
|
||||
/>
|
||||
) : urlError ? (
|
||||
<View style={[styles.messageImage, { justifyContent: 'center', alignItems: 'center', backgroundColor: colors.surface }]}>
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
import { View, Text, StyleSheet, TouchableOpacity, Alert, ScrollView, Platform, useWindowDimensions, TextInput, Modal, ActivityIndicator, Linking, Switch, Image, ActionSheetIOS } from "react-native";
|
||||
import { View, Text, StyleSheet, TouchableOpacity, Alert, ScrollView, Platform, useWindowDimensions, TextInput, Modal, ActivityIndicator, Linking, Switch, ActionSheetIOS } from "react-native";
|
||||
import { Image } from "expo-image";
|
||||
import { useState, useMemo, useEffect } from "react";
|
||||
import { useAuthStore, useMatchStore, useMessageStore, usePacksStore, useSubscriptionStore } from "../../src/store";
|
||||
import { Ionicons } from "@expo/vector-icons";
|
||||
@@ -38,6 +39,7 @@ export default function ProfileScreen() {
|
||||
const [deleteConfirmText, setDeleteConfirmText] = useState("");
|
||||
const [isDeleting, setIsDeleting] = useState(false);
|
||||
const [showFeedbackModal, setShowFeedbackModal] = useState(false);
|
||||
const [isResettingProgress, setIsResettingProgress] = useState(false);
|
||||
const [showPaywall, setShowPaywall] = useState(false);
|
||||
const [showExplicit, setShowExplicit] = useState(user?.show_explicit_content ?? true);
|
||||
const [isUpdatingExplicit, setIsUpdatingExplicit] = useState(false);
|
||||
@@ -381,6 +383,40 @@ export default function ProfileScreen() {
|
||||
);
|
||||
};
|
||||
|
||||
const handleResetProgress = async () => {
|
||||
Alert.alert(
|
||||
"Reset Progress",
|
||||
"This will delete all your swipes, matches, and chat messages. You'll stay connected with your partner and can start fresh. This cannot be undone.",
|
||||
[
|
||||
{ text: "Cancel", style: "cancel" },
|
||||
{
|
||||
text: "Reset",
|
||||
style: "destructive",
|
||||
onPress: async () => {
|
||||
setIsResettingProgress(true);
|
||||
try {
|
||||
const { error } = await supabase.functions.invoke("reset-couple-progress", {
|
||||
method: "DELETE",
|
||||
});
|
||||
|
||||
if (error) throw error;
|
||||
|
||||
// Clear local stores
|
||||
clearMatches();
|
||||
clearMessages();
|
||||
|
||||
Alert.alert("Success", "Your progress has been reset. You can now start swiping again!");
|
||||
} catch (error) {
|
||||
Alert.alert("Error", "Failed to reset progress. Please try again.");
|
||||
} finally {
|
||||
setIsResettingProgress(false);
|
||||
}
|
||||
},
|
||||
},
|
||||
]
|
||||
);
|
||||
};
|
||||
|
||||
const handleSignOut = () => {
|
||||
Alert.alert(
|
||||
"Sign Out",
|
||||
@@ -437,6 +473,8 @@ export default function ProfileScreen() {
|
||||
<Image
|
||||
source={{ uri: user.avatar_url }}
|
||||
style={styles.avatarImage}
|
||||
cachePolicy="disk"
|
||||
transition={200}
|
||||
/>
|
||||
) : (
|
||||
<View style={styles.avatarInner}>
|
||||
@@ -793,10 +831,50 @@ export default function ProfileScreen() {
|
||||
</GlassCard>
|
||||
</Animated.View>
|
||||
|
||||
{/* Reset Progress Section - Only show if in a couple */}
|
||||
{couple && (
|
||||
<Animated.View
|
||||
entering={FadeInDown.delay(500).duration(500)}
|
||||
style={styles.section}
|
||||
>
|
||||
<Text style={styles.resetSectionTitle}>Reset Progress</Text>
|
||||
<GlassCard style={styles.resetCard}>
|
||||
<View style={styles.resetContent}>
|
||||
<View style={styles.resetInfo}>
|
||||
<View style={styles.resetIconContainer}>
|
||||
<Ionicons name="refresh" size={20} color={colors.warning} />
|
||||
</View>
|
||||
<View style={styles.resetTextContainer}>
|
||||
<Text style={styles.resetTitle}>Start Fresh</Text>
|
||||
<Text style={styles.resetDescription}>
|
||||
Delete all swipes, matches, and chats while keeping your partner connection.
|
||||
</Text>
|
||||
</View>
|
||||
</View>
|
||||
<TouchableOpacity
|
||||
style={[styles.resetButton, isResettingProgress && styles.resetButtonDisabled]}
|
||||
onPress={handleResetProgress}
|
||||
disabled={isResettingProgress}
|
||||
activeOpacity={0.7}
|
||||
>
|
||||
{isResettingProgress ? (
|
||||
<ActivityIndicator size="small" color={colors.warning} />
|
||||
) : (
|
||||
<>
|
||||
<Ionicons name="refresh-outline" size={16} color={colors.warning} />
|
||||
<Text style={styles.resetButtonText}>Reset</Text>
|
||||
</>
|
||||
)}
|
||||
</TouchableOpacity>
|
||||
</View>
|
||||
</GlassCard>
|
||||
</Animated.View>
|
||||
)}
|
||||
|
||||
{/* Debug Section - Only show in development builds */}
|
||||
{__DEV__ && (
|
||||
<Animated.View
|
||||
entering={FadeInDown.delay(500).duration(500)}
|
||||
entering={FadeInDown.delay(couple ? 550 : 500).duration(500)}
|
||||
style={styles.section}
|
||||
>
|
||||
<Text style={styles.debugSectionTitle}>Debug</Text>
|
||||
@@ -883,7 +961,7 @@ export default function ProfileScreen() {
|
||||
{/* Danger Zone - Only show if in a couple */}
|
||||
{couple && (
|
||||
<Animated.View
|
||||
entering={FadeInDown.delay(525).duration(500)}
|
||||
entering={FadeInDown.delay(600).duration(500)}
|
||||
style={styles.section}
|
||||
>
|
||||
<Text style={styles.dangerSectionTitle}>Danger Zone</Text>
|
||||
@@ -915,7 +993,7 @@ export default function ProfileScreen() {
|
||||
|
||||
{/* Version */}
|
||||
<Animated.View
|
||||
entering={FadeInDown.delay(couple ? 575 : 525).duration(500)}
|
||||
entering={FadeInDown.delay(couple ? 650 : 525).duration(500)}
|
||||
style={styles.versionContainer}
|
||||
>
|
||||
<View style={styles.versionBadge}>
|
||||
@@ -1349,6 +1427,69 @@ const styles = StyleSheet.create({
|
||||
justifyContent: "center",
|
||||
alignItems: "center",
|
||||
},
|
||||
// Reset Progress styles
|
||||
resetSectionTitle: {
|
||||
...typography.caption1,
|
||||
color: colors.warning,
|
||||
textTransform: "uppercase",
|
||||
letterSpacing: 1.5,
|
||||
marginBottom: spacing.sm,
|
||||
marginLeft: spacing.xs,
|
||||
},
|
||||
resetCard: {
|
||||
borderColor: 'rgba(243, 156, 18, 0.3)',
|
||||
borderWidth: 1,
|
||||
},
|
||||
resetContent: {
|
||||
gap: spacing.md,
|
||||
},
|
||||
resetInfo: {
|
||||
flexDirection: "row",
|
||||
alignItems: "flex-start",
|
||||
},
|
||||
resetIconContainer: {
|
||||
width: 44,
|
||||
height: 44,
|
||||
borderRadius: 22,
|
||||
backgroundColor: colors.warningLight,
|
||||
justifyContent: "center",
|
||||
alignItems: "center",
|
||||
},
|
||||
resetTextContainer: {
|
||||
flex: 1,
|
||||
marginLeft: spacing.md,
|
||||
},
|
||||
resetTitle: {
|
||||
...typography.headline,
|
||||
color: colors.text,
|
||||
marginBottom: spacing.xs,
|
||||
},
|
||||
resetDescription: {
|
||||
...typography.subhead,
|
||||
color: colors.textSecondary,
|
||||
lineHeight: 20,
|
||||
},
|
||||
resetButton: {
|
||||
flexDirection: "row",
|
||||
alignItems: "center",
|
||||
justifyContent: "center",
|
||||
backgroundColor: colors.warningLight,
|
||||
paddingVertical: spacing.sm,
|
||||
paddingHorizontal: spacing.md,
|
||||
borderRadius: radius.md,
|
||||
borderWidth: 1,
|
||||
borderColor: 'rgba(243, 156, 18, 0.3)',
|
||||
gap: spacing.xs,
|
||||
alignSelf: "flex-end",
|
||||
},
|
||||
resetButtonDisabled: {
|
||||
opacity: 0.5,
|
||||
},
|
||||
resetButtonText: {
|
||||
...typography.subhead,
|
||||
color: colors.warning,
|
||||
fontWeight: "600",
|
||||
},
|
||||
// Danger Zone styles
|
||||
dangerSectionTitle: {
|
||||
...typography.caption1,
|
||||
|
||||
@@ -224,20 +224,30 @@ PODS:
|
||||
- Yoga
|
||||
- ExpoAppleAuthentication (7.1.3):
|
||||
- ExpoModulesCore
|
||||
- ExpoAsset (11.0.5):
|
||||
- ExpoModulesCore
|
||||
- ExpoBlur (14.0.3):
|
||||
- ExpoModulesCore
|
||||
- ExpoClipboard (7.0.1):
|
||||
- ExpoModulesCore
|
||||
- ExpoFileSystem (18.0.12):
|
||||
- ExpoModulesCore
|
||||
- ExpoFont (14.0.10):
|
||||
- ExpoFont (13.0.4):
|
||||
- ExpoModulesCore
|
||||
- ExpoHaptics (14.0.1):
|
||||
- ExpoModulesCore
|
||||
- ExpoHead (4.0.22):
|
||||
- ExpoModulesCore
|
||||
- ExpoImage (2.0.7):
|
||||
- ExpoModulesCore
|
||||
- libavif/libdav1d
|
||||
- SDWebImage (~> 5.19.1)
|
||||
- SDWebImageAVIFCoder (~> 0.11.0)
|
||||
- SDWebImageSVGCoder (~> 1.7.0)
|
||||
- ExpoImagePicker (16.0.6):
|
||||
- ExpoModulesCore
|
||||
- ExpoKeepAwake (14.0.3):
|
||||
- ExpoModulesCore
|
||||
- ExpoLinearGradient (14.0.2):
|
||||
- ExpoModulesCore
|
||||
- ExpoLinking (7.0.5):
|
||||
@@ -278,6 +288,11 @@ PODS:
|
||||
- hermes-engine (0.76.9):
|
||||
- hermes-engine/Pre-built (= 0.76.9)
|
||||
- hermes-engine/Pre-built (0.76.9)
|
||||
- libavif/core (0.11.1)
|
||||
- libavif/libdav1d (0.11.1):
|
||||
- libavif/core
|
||||
- libdav1d (>= 0.6.0)
|
||||
- libdav1d (1.2.0)
|
||||
- PurchasesHybridCommon (17.25.0):
|
||||
- RevenueCat (= 5.51.1)
|
||||
- RCT-Folly (2024.10.14.00):
|
||||
@@ -1991,6 +2006,14 @@ PODS:
|
||||
- ReactCommon/turbomodule/core
|
||||
- Sentry/HybridSDK (= 8.48.0)
|
||||
- Yoga
|
||||
- SDWebImage (5.19.7):
|
||||
- SDWebImage/Core (= 5.19.7)
|
||||
- SDWebImage/Core (5.19.7)
|
||||
- SDWebImageAVIFCoder (0.11.1):
|
||||
- libavif/core (>= 0.11.0)
|
||||
- SDWebImage (~> 5.10)
|
||||
- SDWebImageSVGCoder (1.7.0):
|
||||
- SDWebImage/Core (~> 5.6)
|
||||
- Sentry/HybridSDK (8.48.0)
|
||||
- SocketRocket (0.7.1)
|
||||
- Yoga (0.0.0)
|
||||
@@ -1998,30 +2021,33 @@ PODS:
|
||||
DEPENDENCIES:
|
||||
- boost (from `../../../node_modules/react-native/third-party-podspecs/boost.podspec`)
|
||||
- DoubleConversion (from `../../../node_modules/react-native/third-party-podspecs/DoubleConversion.podspec`)
|
||||
- EXApplication (from `../node_modules/expo-application/ios`)
|
||||
- EXConstants (from `../node_modules/expo-constants/ios`)
|
||||
- EXImageLoader (from `../node_modules/expo-image-loader/ios`)
|
||||
- EXApplication (from `../../../node_modules/expo-application/ios`)
|
||||
- EXConstants (from `../../../node_modules/expo-constants/ios`)
|
||||
- EXImageLoader (from `../../../node_modules/expo-image-loader/ios`)
|
||||
- EXJSONUtils (from `../../../node_modules/expo-json-utils/ios`)
|
||||
- EXManifests (from `../../../node_modules/expo-manifests/ios`)
|
||||
- EXNotifications (from `../node_modules/expo-notifications/ios`)
|
||||
- EXNotifications (from `../../../node_modules/expo-notifications/ios`)
|
||||
- Expo (from `../../../node_modules/expo`)
|
||||
- expo-dev-client (from `../../../node_modules/expo-dev-client/ios`)
|
||||
- expo-dev-launcher (from `../../../node_modules/expo-dev-launcher`)
|
||||
- expo-dev-menu (from `../../../node_modules/expo-dev-menu`)
|
||||
- expo-dev-menu-interface (from `../../../node_modules/expo-dev-menu-interface/ios`)
|
||||
- ExpoAppleAuthentication (from `../../../node_modules/expo-apple-authentication/ios`)
|
||||
- ExpoBlur (from `../node_modules/expo-blur/ios`)
|
||||
- ExpoClipboard (from `../node_modules/expo-clipboard/ios`)
|
||||
- ExpoFileSystem (from `../node_modules/expo-file-system/ios`)
|
||||
- ExpoFont (from `../node_modules/expo-font/ios`)
|
||||
- ExpoHaptics (from `../node_modules/expo-haptics/ios`)
|
||||
- ExpoHead (from `../node_modules/expo-router/ios`)
|
||||
- ExpoImagePicker (from `../node_modules/expo-image-picker/ios`)
|
||||
- ExpoLinearGradient (from `../node_modules/expo-linear-gradient/ios`)
|
||||
- ExpoLinking (from `../node_modules/expo-linking/ios`)
|
||||
- ExpoAsset (from `../../../node_modules/expo-asset/ios`)
|
||||
- ExpoBlur (from `../../../node_modules/expo-blur/ios`)
|
||||
- ExpoClipboard (from `../../../node_modules/expo-clipboard/ios`)
|
||||
- ExpoFileSystem (from `../../../node_modules/expo-file-system/ios`)
|
||||
- ExpoFont (from `../../../node_modules/expo-font/ios`)
|
||||
- ExpoHaptics (from `../../../node_modules/expo-haptics/ios`)
|
||||
- ExpoHead (from `../../../node_modules/expo-router/ios`)
|
||||
- ExpoImage (from `../../../node_modules/expo-image/ios`)
|
||||
- ExpoImagePicker (from `../../../node_modules/expo-image-picker/ios`)
|
||||
- ExpoKeepAwake (from `../../../node_modules/expo-keep-awake/ios`)
|
||||
- ExpoLinearGradient (from `../../../node_modules/expo-linear-gradient/ios`)
|
||||
- ExpoLinking (from `../../../node_modules/expo-linking/ios`)
|
||||
- ExpoLocalAuthentication (from `../../../node_modules/expo-local-authentication/ios`)
|
||||
- ExpoModulesCore (from `../../../node_modules/expo-modules-core`)
|
||||
- ExpoSecureStore (from `../node_modules/expo-secure-store/ios`)
|
||||
- ExpoSecureStore (from `../../../node_modules/expo-secure-store/ios`)
|
||||
- EXUpdatesInterface (from `../../../node_modules/expo-updates-interface/ios`)
|
||||
- fast_float (from `../../../node_modules/react-native/third-party-podspecs/fast_float.podspec`)
|
||||
- FBLazyVector (from `../../../node_modules/react-native/Libraries/FBLazyVector`)
|
||||
@@ -2059,7 +2085,7 @@ DEPENDENCIES:
|
||||
- React-logger (from `../../../node_modules/react-native/ReactCommon/logger`)
|
||||
- React-Mapbuffer (from `../../../node_modules/react-native/ReactCommon`)
|
||||
- React-microtasksnativemodule (from `../../../node_modules/react-native/ReactCommon/react/nativemodule/microtasks`)
|
||||
- react-native-safe-area-context (from `../node_modules/react-native-safe-area-context`)
|
||||
- react-native-safe-area-context (from `../../../node_modules/react-native-safe-area-context`)
|
||||
- React-nativeconfig (from `../../../node_modules/react-native/ReactCommon`)
|
||||
- React-NativeModulesApple (from `../../../node_modules/react-native/ReactCommon/react/nativemodule/core/platform/ios`)
|
||||
- React-perflogger (from `../../../node_modules/react-native/ReactCommon/reactperflogger`)
|
||||
@@ -2088,17 +2114,22 @@ DEPENDENCIES:
|
||||
- ReactCodegen (from `build/generated/ios`)
|
||||
- ReactCommon/turbomodule/core (from `../../../node_modules/react-native/ReactCommon`)
|
||||
- "RNCAsyncStorage (from `../../../node_modules/@react-native-async-storage/async-storage`)"
|
||||
- RNGestureHandler (from `../node_modules/react-native-gesture-handler`)
|
||||
- RNGestureHandler (from `../../../node_modules/react-native-gesture-handler`)
|
||||
- RNPurchases (from `../../../node_modules/react-native-purchases`)
|
||||
- RNReanimated (from `../node_modules/react-native-reanimated`)
|
||||
- RNReanimated (from `../../../node_modules/react-native-reanimated`)
|
||||
- RNScreens (from `../../../node_modules/react-native-screens`)
|
||||
- "RNSentry (from `../../../node_modules/@sentry/react-native`)"
|
||||
- Yoga (from `../../../node_modules/react-native/ReactCommon/yoga`)
|
||||
|
||||
SPEC REPOS:
|
||||
trunk:
|
||||
- libavif
|
||||
- libdav1d
|
||||
- PurchasesHybridCommon
|
||||
- RevenueCat
|
||||
- SDWebImage
|
||||
- SDWebImageAVIFCoder
|
||||
- SDWebImageSVGCoder
|
||||
- Sentry
|
||||
- SocketRocket
|
||||
|
||||
@@ -2108,17 +2139,17 @@ EXTERNAL SOURCES:
|
||||
DoubleConversion:
|
||||
:podspec: "../../../node_modules/react-native/third-party-podspecs/DoubleConversion.podspec"
|
||||
EXApplication:
|
||||
:path: "../node_modules/expo-application/ios"
|
||||
:path: "../../../node_modules/expo-application/ios"
|
||||
EXConstants:
|
||||
:path: "../node_modules/expo-constants/ios"
|
||||
:path: "../../../node_modules/expo-constants/ios"
|
||||
EXImageLoader:
|
||||
:path: "../node_modules/expo-image-loader/ios"
|
||||
:path: "../../../node_modules/expo-image-loader/ios"
|
||||
EXJSONUtils:
|
||||
:path: "../../../node_modules/expo-json-utils/ios"
|
||||
EXManifests:
|
||||
:path: "../../../node_modules/expo-manifests/ios"
|
||||
EXNotifications:
|
||||
:path: "../node_modules/expo-notifications/ios"
|
||||
:path: "../../../node_modules/expo-notifications/ios"
|
||||
Expo:
|
||||
:path: "../../../node_modules/expo"
|
||||
expo-dev-client:
|
||||
@@ -2131,30 +2162,36 @@ EXTERNAL SOURCES:
|
||||
:path: "../../../node_modules/expo-dev-menu-interface/ios"
|
||||
ExpoAppleAuthentication:
|
||||
:path: "../../../node_modules/expo-apple-authentication/ios"
|
||||
ExpoAsset:
|
||||
:path: "../../../node_modules/expo-asset/ios"
|
||||
ExpoBlur:
|
||||
:path: "../node_modules/expo-blur/ios"
|
||||
:path: "../../../node_modules/expo-blur/ios"
|
||||
ExpoClipboard:
|
||||
:path: "../node_modules/expo-clipboard/ios"
|
||||
:path: "../../../node_modules/expo-clipboard/ios"
|
||||
ExpoFileSystem:
|
||||
:path: "../node_modules/expo-file-system/ios"
|
||||
:path: "../../../node_modules/expo-file-system/ios"
|
||||
ExpoFont:
|
||||
:path: "../node_modules/expo-font/ios"
|
||||
:path: "../../../node_modules/expo-font/ios"
|
||||
ExpoHaptics:
|
||||
:path: "../node_modules/expo-haptics/ios"
|
||||
:path: "../../../node_modules/expo-haptics/ios"
|
||||
ExpoHead:
|
||||
:path: "../node_modules/expo-router/ios"
|
||||
:path: "../../../node_modules/expo-router/ios"
|
||||
ExpoImage:
|
||||
:path: "../../../node_modules/expo-image/ios"
|
||||
ExpoImagePicker:
|
||||
:path: "../node_modules/expo-image-picker/ios"
|
||||
:path: "../../../node_modules/expo-image-picker/ios"
|
||||
ExpoKeepAwake:
|
||||
:path: "../../../node_modules/expo-keep-awake/ios"
|
||||
ExpoLinearGradient:
|
||||
:path: "../node_modules/expo-linear-gradient/ios"
|
||||
:path: "../../../node_modules/expo-linear-gradient/ios"
|
||||
ExpoLinking:
|
||||
:path: "../node_modules/expo-linking/ios"
|
||||
:path: "../../../node_modules/expo-linking/ios"
|
||||
ExpoLocalAuthentication:
|
||||
:path: "../../../node_modules/expo-local-authentication/ios"
|
||||
ExpoModulesCore:
|
||||
:path: "../../../node_modules/expo-modules-core"
|
||||
ExpoSecureStore:
|
||||
:path: "../node_modules/expo-secure-store/ios"
|
||||
:path: "../../../node_modules/expo-secure-store/ios"
|
||||
EXUpdatesInterface:
|
||||
:path: "../../../node_modules/expo-updates-interface/ios"
|
||||
fast_float:
|
||||
@@ -2227,7 +2264,7 @@ EXTERNAL SOURCES:
|
||||
React-microtasksnativemodule:
|
||||
:path: "../../../node_modules/react-native/ReactCommon/react/nativemodule/microtasks"
|
||||
react-native-safe-area-context:
|
||||
:path: "../node_modules/react-native-safe-area-context"
|
||||
:path: "../../../node_modules/react-native-safe-area-context"
|
||||
React-nativeconfig:
|
||||
:path: "../../../node_modules/react-native/ReactCommon"
|
||||
React-NativeModulesApple:
|
||||
@@ -2285,11 +2322,11 @@ EXTERNAL SOURCES:
|
||||
RNCAsyncStorage:
|
||||
:path: "../../../node_modules/@react-native-async-storage/async-storage"
|
||||
RNGestureHandler:
|
||||
:path: "../node_modules/react-native-gesture-handler"
|
||||
:path: "../../../node_modules/react-native-gesture-handler"
|
||||
RNPurchases:
|
||||
:path: "../../../node_modules/react-native-purchases"
|
||||
RNReanimated:
|
||||
:path: "../node_modules/react-native-reanimated"
|
||||
:path: "../../../node_modules/react-native-reanimated"
|
||||
RNScreens:
|
||||
:path: "../../../node_modules/react-native-screens"
|
||||
RNSentry:
|
||||
@@ -2312,13 +2349,16 @@ SPEC CHECKSUMS:
|
||||
expo-dev-menu: 3fda3eb2b3394dc92955f6ff60cb278f843641be
|
||||
expo-dev-menu-interface: 00dc42302a72722fdecec3fa048de84a9133bcc4
|
||||
ExpoAppleAuthentication: 52631ed9dcb71c65712a447bbb9a5667bb8fcf0c
|
||||
ExpoAsset: 48386d40d53a8c1738929b3ed509bcad595b5516
|
||||
ExpoBlur: 392c1207f71d0ecf22371621c1fbd44ba84d9742
|
||||
ExpoClipboard: 44fd1c8959ee8f6175d059dc011b154c9709a969
|
||||
ExpoFileSystem: 42d363d3b96f9afab980dcef60d5657a4443c655
|
||||
ExpoFont: 35ac6191ed86bbf56b3ebd2d9154eda9fad5b509
|
||||
ExpoFont: f354e926f8feae5e831ec8087f36652b44a0b188
|
||||
ExpoHaptics: 8d199b2f33245ea85289ff6c954c7ee7c00a5b5d
|
||||
ExpoHead: 2a14db58219c0a506e4a409ea83f884eab23f555
|
||||
ExpoImage: d840b256050f4428d2942bc2b6e9251f9e0d7021
|
||||
ExpoImagePicker: 24e5ba8da111f74519b1e6dc556e0b438b2b8464
|
||||
ExpoKeepAwake: b0171a73665bfcefcfcc311742a72a956e6aa680
|
||||
ExpoLinearGradient: 35ebd83b16f80b3add053a2fd68cc328ed927f60
|
||||
ExpoLinking: 8d12bee174ba0cdf31239706578e29e74a417402
|
||||
ExpoLocalAuthentication: eb2be9c7bcdc68e9434d4be4bb5aa5cf7943e816
|
||||
@@ -2330,6 +2370,8 @@ SPEC CHECKSUMS:
|
||||
fmt: 01b82d4ca6470831d1cc0852a1af644be019e8f6
|
||||
glog: 08b301085f15bcbb6ff8632a8ebaf239aae04e6a
|
||||
hermes-engine: 9e868dc7be781364296d6ee2f56d0c1a9ef0bb11
|
||||
libavif: 84bbb62fb232c3018d6f1bab79beea87e35de7b7
|
||||
libdav1d: 23581a4d8ec811ff171ed5e2e05cd27bad64c39f
|
||||
PurchasesHybridCommon: 6a79a873ab52f777bfa36e9516f3fcd84d3b3428
|
||||
RCT-Folly: ea9d9256ba7f9322ef911169a9f696e5857b9e17
|
||||
RCTDeprecation: ebe712bb05077934b16c6bf25228bdec34b64f83
|
||||
@@ -2392,9 +2434,12 @@ SPEC CHECKSUMS:
|
||||
RNCAsyncStorage: aa75595c1aefa18f868452091fa0c411a516ce11
|
||||
RNGestureHandler: 32336458ef842088a459ccfcc10b583186de2633
|
||||
RNPurchases: cc3333cede6c0f040545a95c38ae627d0174c058
|
||||
RNReanimated: 76053156ece6bf6e6bdf8a7d03db216285e4719c
|
||||
RNReanimated: 82bf292e3c590d2d9886039f83e1baf0c1b2cdc4
|
||||
RNScreens: 295d9c0aaeb7f680d03d7e9b476569a4959aae89
|
||||
RNSentry: 41eab31e46a191a4f9296d64a318b2a6bf0f98c5
|
||||
SDWebImage: 8a6b7b160b4d710e2a22b6900e25301075c34cb3
|
||||
SDWebImageAVIFCoder: afe194a084e851f70228e4be35ef651df0fc5c57
|
||||
SDWebImageSVGCoder: 15a300a97ec1c8ac958f009c02220ac0402e936c
|
||||
Sentry: 1ca8405451040482877dcd344dfa3ef80b646631
|
||||
SocketRocket: d4aabe649be1e368d1318fdf28a022d714d65748
|
||||
Yoga: feb4910aba9742cfedc059e2b2902e22ffe9954a
|
||||
|
||||
@@ -175,6 +175,8 @@
|
||||
TargetAttributes = {
|
||||
13B07F861A680F5B00A75B9A = {
|
||||
LastSwiftMigration = 1250;
|
||||
DevelopmentTeam = "BXVPBATDZY";
|
||||
ProvisioningStyle = Automatic;
|
||||
};
|
||||
};
|
||||
};
|
||||
@@ -280,6 +282,7 @@
|
||||
"${PODS_CONFIGURATION_BUILD_DIR}/React-Core/React-Core_privacy.bundle",
|
||||
"${PODS_CONFIGURATION_BUILD_DIR}/React-cxxreact/React-cxxreact_privacy.bundle",
|
||||
"${PODS_CONFIGURATION_BUILD_DIR}/RevenueCat/RevenueCat.bundle",
|
||||
"${PODS_CONFIGURATION_BUILD_DIR}/SDWebImage/SDWebImage.bundle",
|
||||
"${PODS_CONFIGURATION_BUILD_DIR}/Sentry/Sentry.bundle",
|
||||
"${PODS_CONFIGURATION_BUILD_DIR}/boost/boost_privacy.bundle",
|
||||
"${PODS_CONFIGURATION_BUILD_DIR}/expo-dev-launcher/EXDevLauncher.bundle",
|
||||
@@ -299,6 +302,7 @@
|
||||
"${TARGET_BUILD_DIR}/${UNLOCALIZED_RESOURCES_FOLDER_PATH}/React-Core_privacy.bundle",
|
||||
"${TARGET_BUILD_DIR}/${UNLOCALIZED_RESOURCES_FOLDER_PATH}/React-cxxreact_privacy.bundle",
|
||||
"${TARGET_BUILD_DIR}/${UNLOCALIZED_RESOURCES_FOLDER_PATH}/RevenueCat.bundle",
|
||||
"${TARGET_BUILD_DIR}/${UNLOCALIZED_RESOURCES_FOLDER_PATH}/SDWebImage.bundle",
|
||||
"${TARGET_BUILD_DIR}/${UNLOCALIZED_RESOURCES_FOLDER_PATH}/Sentry.bundle",
|
||||
"${TARGET_BUILD_DIR}/${UNLOCALIZED_RESOURCES_FOLDER_PATH}/boost_privacy.bundle",
|
||||
"${TARGET_BUILD_DIR}/${UNLOCALIZED_RESOURCES_FOLDER_PATH}/EXDevLauncher.bundle",
|
||||
@@ -394,6 +398,9 @@
|
||||
SWIFT_VERSION = 5.0;
|
||||
TARGETED_DEVICE_FAMILY = 1;
|
||||
VERSIONING_SYSTEM = "apple-generic";
|
||||
DEVELOPMENT_TEAM = "BXVPBATDZY";
|
||||
CODE_SIGN_IDENTITY = "Apple Development";
|
||||
CODE_SIGN_STYLE = Automatic;
|
||||
};
|
||||
name = Debug;
|
||||
};
|
||||
@@ -421,6 +428,9 @@
|
||||
SWIFT_VERSION = 5.0;
|
||||
TARGETED_DEVICE_FAMILY = 1;
|
||||
VERSIONING_SYSTEM = "apple-generic";
|
||||
DEVELOPMENT_TEAM = "BXVPBATDZY";
|
||||
CODE_SIGN_IDENTITY = "Apple Development";
|
||||
CODE_SIGN_STYLE = Automatic;
|
||||
};
|
||||
name = Release;
|
||||
};
|
||||
|
||||
@@ -34,6 +34,7 @@
|
||||
"expo-file-system": "~18.0.12",
|
||||
"expo-font": "~13.0.4",
|
||||
"expo-haptics": "~14.0.1",
|
||||
"expo-image": "~2.0.7",
|
||||
"expo-image-picker": "~16.0.6",
|
||||
"expo-linear-gradient": "~14.0.2",
|
||||
"expo-linking": "~7.0.0",
|
||||
|
||||
@@ -0,0 +1,95 @@
|
||||
import { supabase } from './supabase';
|
||||
|
||||
// Cache for signed URLs with expiration tracking
|
||||
interface CachedUrl {
|
||||
url: string;
|
||||
expiresAt: number;
|
||||
}
|
||||
|
||||
const signedUrlCache = new Map<string, CachedUrl>();
|
||||
|
||||
// Buffer time before expiration to refresh URL (5 minutes)
|
||||
const EXPIRATION_BUFFER_MS = 5 * 60 * 1000;
|
||||
// Default signed URL expiry (1 hour)
|
||||
const SIGNED_URL_EXPIRY_SECONDS = 3600;
|
||||
|
||||
/**
|
||||
* Get a signed URL for a chat media file with caching.
|
||||
* Returns cached URL if still valid, otherwise generates a new one.
|
||||
*/
|
||||
export async function getCachedSignedUrl(
|
||||
storagePath: string,
|
||||
bucket: string = 'chat-media'
|
||||
): Promise<string | null> {
|
||||
const cacheKey = `${bucket}:${storagePath}`;
|
||||
const cached = signedUrlCache.get(cacheKey);
|
||||
const now = Date.now();
|
||||
|
||||
// Return cached URL if it's still valid (with buffer time)
|
||||
if (cached && cached.expiresAt - EXPIRATION_BUFFER_MS > now) {
|
||||
return cached.url;
|
||||
}
|
||||
|
||||
// Generate new signed URL
|
||||
const { data, error } = await supabase.storage
|
||||
.from(bucket)
|
||||
.createSignedUrl(storagePath, SIGNED_URL_EXPIRY_SECONDS);
|
||||
|
||||
if (error || !data?.signedUrl) {
|
||||
console.error('Failed to get signed URL:', error);
|
||||
return null;
|
||||
}
|
||||
|
||||
// Cache the new URL
|
||||
signedUrlCache.set(cacheKey, {
|
||||
url: data.signedUrl,
|
||||
expiresAt: now + SIGNED_URL_EXPIRY_SECONDS * 1000,
|
||||
});
|
||||
|
||||
return data.signedUrl;
|
||||
}
|
||||
|
||||
/**
|
||||
* Extract storage path from media_path (handles both old full URLs and new path-only format)
|
||||
*/
|
||||
export function getStoragePath(mediaPath: string): string {
|
||||
if (mediaPath.startsWith('http')) {
|
||||
// Old format: extract path from full URL
|
||||
const match = mediaPath.match(/\/chat-media\/(.+)$/);
|
||||
return match ? match[1] : mediaPath;
|
||||
}
|
||||
return mediaPath;
|
||||
}
|
||||
|
||||
/**
|
||||
* Clear expired URLs from the cache to free memory.
|
||||
* Call this periodically or when memory pressure is detected.
|
||||
*/
|
||||
export function clearExpiredUrls(): void {
|
||||
const now = Date.now();
|
||||
for (const [key, cached] of signedUrlCache) {
|
||||
if (cached.expiresAt < now) {
|
||||
signedUrlCache.delete(key);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Clear all cached URLs.
|
||||
*/
|
||||
export function clearUrlCache(): void {
|
||||
signedUrlCache.clear();
|
||||
}
|
||||
|
||||
/**
|
||||
* Prefetch signed URLs for a list of media paths.
|
||||
* Useful for preloading images when entering a chat.
|
||||
*/
|
||||
export async function prefetchSignedUrls(
|
||||
mediaPaths: string[],
|
||||
bucket: string = 'chat-media'
|
||||
): Promise<void> {
|
||||
await Promise.all(
|
||||
mediaPaths.map(path => getCachedSignedUrl(getStoragePath(path), bucket))
|
||||
);
|
||||
}
|
||||
Reference in new issue
Block a user