Add real-time listeners and pull-to-refresh

This commit is contained in:
Sterling Archer
2026-06-18 14:12:29 -07:00
parent dcf8f11b8c
commit 968d55131d
+89 -10
View File
@@ -864,7 +864,24 @@
.tab-btn { font-size: 12px; padding: 8px 2px; }
}
/* ── SPINNER ── */
/* ── PULL TO REFRESH ── */
.ptr-indicator {
position: fixed;
top: 0;
left: 50%;
transform: translateX(-50%) translateY(-48px);
background: var(--surface2);
border: 1px solid var(--border);
border-radius: 20px;
padding: 8px 16px;
font-size: 13px;
color: var(--teal);
z-index: 200;
transition: transform 0.2s;
pointer-events: none;
white-space: nowrap;
}
.ptr-indicator.visible { transform: translateX(-50%) translateY(12px); }
.spinner {
display: inline-block;
width: 18px;
@@ -1218,6 +1235,8 @@
</div><!-- /app -->
<div id="ptr-indicator" class="ptr-indicator">↓ Refreshing…</div>
<div id="toast"></div>
<!-- Firebase SDK -->
@@ -1567,22 +1586,34 @@ function todayKey() {
}
// ─────────────────────────────────────────────
// FRIENDS SYSTEM
// FRIENDS SYSTEM — REAL-TIME LISTENERS
// ─────────────────────────────────────────────
let friendsList = [];
let pendingReceived = [];
let pendingSent = [];
let friendUnsubscribers = [];
async function loadFriends() {
function teardownListeners() {
friendUnsubscribers.forEach(fn => fn());
friendUnsubscribers = [];
}
function loadFriends() {
if (!currentUser) return;
teardownListeners();
const uid = currentUser.uid;
const frRef = collection(db, 'friendRequests');
const q1 = query(frRef, where('toUid', '==', uid));
const q2 = query(frRef, where('fromUid', '==', uid));
const [receivedSnap, sentSnap] = await Promise.all([getDocs(q1), getDocs(q2)]);
const unsubOwn = onSnapshot(doc(db, 'users', uid), snap => {
if (!snap.exists()) return;
currentUserData = { ...currentUserData, ...snap.data() };
renderMySteps();
checkSetupReminder();
});
friendUnsubscribers.push(unsubOwn);
const unsubReceived = onSnapshot(query(frRef, where('toUid', '==', uid)), async receivedSnap => {
const unsubSent = onSnapshot(query(frRef, where('fromUid', '==', uid)), async sentSnap => {
pendingReceived = receivedSnap.docs
.filter(d => d.data().status === 'pending')
.map(d => ({ id: d.id, ...d.data() }));
@@ -1601,15 +1632,33 @@ async function loadFriends() {
const allFriendUids = [...new Set([...confirmedFromMe, ...confirmedToMe])];
friendUnsubscribers.filter(fn => fn._isFriendProfile).forEach(fn => fn());
friendUnsubscribers = friendUnsubscribers.filter(fn => !fn._isFriendProfile);
friendsList = [];
for (const fuid of allFriendUids) {
const snap = await getDoc(doc(db, 'profiles', fuid));
if (snap.exists()) friendsList.push({ uid: fuid, ...snap.data() });
const unsubProfile = onSnapshot(doc(db, 'profiles', fuid), profileSnap => {
if (!profileSnap.exists()) return;
const idx = friendsList.findIndex(f => f.uid === fuid);
const data = { uid: fuid, ...profileSnap.data() };
if (idx >= 0) {
friendsList[idx] = data;
} else {
friendsList.push(data);
}
renderFriendSteps();
renderFriendsPanel();
});
unsubProfile._isFriendProfile = true;
friendUnsubscribers.push(unsubProfile);
}
renderFriendsPanel();
updateBadge();
});
friendUnsubscribers.push(unsubSent);
});
friendUnsubscribers.push(unsubReceived);
}
function avatarHtml(f, size=44) {
@@ -1867,9 +1916,11 @@ window.saveProfile = async function() {
// ─────────────────────────────────────────────
window.signOut = async function() {
if (!confirm('Sign out?')) return;
teardownListeners();
await fbSignOut(auth);
currentUser = null;
currentUserData = null;
friendsList = [];
showScreen('auth');
showAuthForm();
}
@@ -1904,6 +1955,34 @@ document.getElementById('profile-avatar-input').addEventListener('input', update
document.addEventListener('keydown', e => {
if (e.key === 'Escape') closeProfileModal();
});
let ptrStartY = 0;
let ptrTriggered = false;
document.addEventListener('touchstart', e => {
if (window.scrollY === 0) ptrStartY = e.touches[0].clientY;
}, { passive: true });
document.addEventListener('touchmove', e => {
if (!ptrStartY) return;
const delta = e.touches[0].clientY - ptrStartY;
if (delta > 80 && !ptrTriggered && document.getElementById('screen-dashboard').classList.contains('active')) {
ptrTriggered = true;
const ind = document.getElementById('ptr-indicator');
ind.classList.add('visible');
}
}, { passive: true });
document.addEventListener('touchend', () => {
if (ptrTriggered) {
teardownListeners();
loadFriends();
setTimeout(() => {
document.getElementById('ptr-indicator').classList.remove('visible');
}, 1000);
}
ptrStartY = 0;
ptrTriggered = false;
}, { passive: true });
</script>
</body>
</html>