mirror of
https://github.com/jjbrunton/Sauci.git
synced 2026-10-31 10:19:43 +00:00
Fix chat message race condition on notification tap
This commit is contained in:
1 parent
0711080112
commit
45499edc9a
5 files changed
+27
-17
No files matched your search
@@ -82,11 +82,6 @@ export function ContentTile({ pack, isLocked, isNew = false, progress, onPress }
|
|||||||
|
|
||||||
{/* Content */}
|
{/* Content */}
|
||||||
<View style={styles.content}>
|
<View style={styles.content}>
|
||||||
{pack.category?.name && (
|
|
||||||
<Text style={styles.categoryText} numberOfLines={1}>
|
|
||||||
{pack.category.name}
|
|
||||||
</Text>
|
|
||||||
)}
|
|
||||||
<Text style={styles.titleText} numberOfLines={2}>
|
<Text style={styles.titleText} numberOfLines={2}>
|
||||||
{pack.name}
|
{pack.name}
|
||||||
</Text>
|
</Text>
|
||||||
@@ -173,13 +168,6 @@ const styles = StyleSheet.create({
|
|||||||
justifyContent: 'center',
|
justifyContent: 'center',
|
||||||
marginTop: spacing.sm,
|
marginTop: spacing.sm,
|
||||||
},
|
},
|
||||||
categoryText: {
|
|
||||||
...typography.caption2,
|
|
||||||
color: 'rgba(255, 255, 255, 0.7)',
|
|
||||||
textTransform: 'uppercase',
|
|
||||||
letterSpacing: 1,
|
|
||||||
marginBottom: 2,
|
|
||||||
},
|
|
||||||
titleText: {
|
titleText: {
|
||||||
...typography.headline,
|
...typography.headline,
|
||||||
color: colors.text,
|
color: colors.text,
|
||||||
|
|||||||
@@ -20,7 +20,7 @@ import { SharePreviewModal } from "../share";
|
|||||||
const ACTION_BUTTON_SIZE = 72;
|
const ACTION_BUTTON_SIZE = 72;
|
||||||
const ACTION_BUTTON_OVERHANG_PERCENT = 0.5; // 50% of button hangs below the card
|
const ACTION_BUTTON_OVERHANG_PERCENT = 0.5; // 50% of button hangs below the card
|
||||||
const actionButtonOffset = ACTION_BUTTON_SIZE * ACTION_BUTTON_OVERHANG_PERCENT;
|
const actionButtonOffset = ACTION_BUTTON_SIZE * ACTION_BUTTON_OVERHANG_PERCENT;
|
||||||
const actionButtonBackground = colors.muted;
|
const actionButtonBackground = '#3a3a42';
|
||||||
|
|
||||||
// Haptics helper - not supported on web
|
// Haptics helper - not supported on web
|
||||||
const triggerHaptic = async (style: 'light' | 'medium' | 'heavy' = 'medium') => {
|
const triggerHaptic = async (style: 'light' | 'medium' | 'heavy' = 'medium') => {
|
||||||
|
|||||||
@@ -121,7 +121,7 @@ export function ProfileScreen() {
|
|||||||
</LinearGradient>
|
</LinearGradient>
|
||||||
<View style={styles.profileInfo}>
|
<View style={styles.profileInfo}>
|
||||||
<Text style={styles.userName}>{user?.name || "User"}</Text>
|
<Text style={styles.userName}>{user?.name || "User"}</Text>
|
||||||
<Text style={styles.userEmail}>
|
<Text style={styles.userEmail} numberOfLines={1} ellipsizeMode="middle">
|
||||||
{isAnonymous ? "Unsaved account" : (user?.email || "")}
|
{isAnonymous ? "Unsaved account" : (user?.email || "")}
|
||||||
</Text>
|
</Text>
|
||||||
</View>
|
</View>
|
||||||
|
|||||||
@@ -260,7 +260,7 @@ export function ProfileSettingsScreen() {
|
|||||||
color={colors.textTertiary}
|
color={colors.textTertiary}
|
||||||
style={styles.inputIcon}
|
style={styles.inputIcon}
|
||||||
/>
|
/>
|
||||||
<Text style={styles.readOnlyText}>
|
<Text style={styles.readOnlyText} numberOfLines={1} ellipsizeMode="middle">
|
||||||
{authEmail || (isAnonymous ? 'Guest account' : 'Loading...')}
|
{authEmail || (isAnonymous ? 'Guest account' : 'Loading...')}
|
||||||
</Text>
|
</Text>
|
||||||
</View>
|
</View>
|
||||||
@@ -407,6 +407,7 @@ const styles = StyleSheet.create({
|
|||||||
readOnlyText: {
|
readOnlyText: {
|
||||||
...typography.body,
|
...typography.body,
|
||||||
color: colors.textSecondary,
|
color: colors.textSecondary,
|
||||||
|
flex: 1,
|
||||||
},
|
},
|
||||||
// Gender
|
// Gender
|
||||||
genderDescription: {
|
genderDescription: {
|
||||||
|
|||||||
@@ -41,6 +41,10 @@ export const useMessageSubscription = (
|
|||||||
const [loading, setLoading] = useState(true);
|
const [loading, setLoading] = useState(true);
|
||||||
const isFocusedRef = useRef(false);
|
const isFocusedRef = useRef(false);
|
||||||
|
|
||||||
|
// Track messages that arrive via real-time during initial fetch to avoid race condition
|
||||||
|
const pendingRealtimeMessagesRef = useRef<Message[]>([]);
|
||||||
|
const isFetchingRef = useRef(false);
|
||||||
|
|
||||||
// Filter out messages that the user has deleted for themselves
|
// Filter out messages that the user has deleted for themselves
|
||||||
const messages = useMemo(() => {
|
const messages = useMemo(() => {
|
||||||
return allMessages.filter(m => !deletedMessageIds.has(m.id));
|
return allMessages.filter(m => !deletedMessageIds.has(m.id));
|
||||||
@@ -104,6 +108,8 @@ export const useMessageSubscription = (
|
|||||||
|
|
||||||
const fetchMessagesAndMarkRead = async () => {
|
const fetchMessagesAndMarkRead = async () => {
|
||||||
setLoading(true);
|
setLoading(true);
|
||||||
|
isFetchingRef.current = true;
|
||||||
|
pendingRealtimeMessagesRef.current = [];
|
||||||
setAllMessages([]); // Clear previous chat's messages immediately
|
setAllMessages([]); // Clear previous chat's messages immediately
|
||||||
|
|
||||||
const { data } = await supabase
|
const { data } = await supabase
|
||||||
@@ -113,7 +119,14 @@ export const useMessageSubscription = (
|
|||||||
.order('created_at', { ascending: false });
|
.order('created_at', { ascending: false });
|
||||||
|
|
||||||
if (data) {
|
if (data) {
|
||||||
setAllMessages(data);
|
// Merge any messages that arrived via real-time during the fetch
|
||||||
|
const pendingMessages = pendingRealtimeMessagesRef.current;
|
||||||
|
const fetchedIds = new Set(data.map(m => m.id));
|
||||||
|
const newRealtimeMessages = pendingMessages.filter(m => !fetchedIds.has(m.id));
|
||||||
|
|
||||||
|
// Combine: new real-time messages first (newest), then fetched data
|
||||||
|
const mergedMessages = [...newRealtimeMessages, ...data];
|
||||||
|
setAllMessages(mergedMessages);
|
||||||
|
|
||||||
const now = new Date().toISOString();
|
const now = new Date().toISOString();
|
||||||
|
|
||||||
@@ -145,6 +158,8 @@ export const useMessageSubscription = (
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
isFetchingRef.current = false;
|
||||||
|
pendingRealtimeMessagesRef.current = [];
|
||||||
setLoading(false);
|
setLoading(false);
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -179,7 +194,13 @@ export const useMessageSubscription = (
|
|||||||
newMessage.read_at = now;
|
newMessage.read_at = now;
|
||||||
}
|
}
|
||||||
|
|
||||||
setAllMessages(prev => [newMessage, ...prev]);
|
// If initial fetch is in progress, queue the message to avoid race condition
|
||||||
|
// where setAllMessages(data) would overwrite this message
|
||||||
|
if (isFetchingRef.current) {
|
||||||
|
pendingRealtimeMessagesRef.current.push(newMessage);
|
||||||
|
} else {
|
||||||
|
setAllMessages(prev => [newMessage, ...prev]);
|
||||||
|
}
|
||||||
onNewMessage?.();
|
onNewMessage?.();
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
|
|||||||
Reference in new issue
Block a user