From d6b6906f5ee6dcf3f627eac692709b31d92ca91a Mon Sep 17 00:00:00 2001 From: Sahil Garg Date: Fri, 12 Jun 2026 09:19:04 +0530 Subject: [PATCH] feat: added pull-to-refresh on home screen. --- .../src/routes/(app)/main/+page.svelte | 130 ++++++++++++++++-- 1 file changed, 119 insertions(+), 11 deletions(-) diff --git a/infrastructure/eid-wallet/src/routes/(app)/main/+page.svelte b/infrastructure/eid-wallet/src/routes/(app)/main/+page.svelte index f42ea6ca8..7610c802e 100644 --- a/infrastructure/eid-wallet/src/routes/(app)/main/+page.svelte +++ b/infrastructure/eid-wallet/src/routes/(app)/main/+page.svelte @@ -124,6 +124,82 @@ let socialBindingPreview = $state( ); const verified = $derived(isFake === false || legalId !== null); +// ── Pull-to-refresh ─────────────────────────────────────────────────────────── +const PULL_THRESHOLD = 72; +const PULL_MAX = 100; +let pullState = $state<"idle" | "pulling" | "triggered" | "refreshing">("idle"); +let pullDistance = $state(0); +let _pullStartY = 0; +let _pullTracking = false; + +function getPullScrollTop(): number { + const el = document.querySelector("[data-route-wrapper]") as HTMLElement | null; + return el?.scrollTop ?? 0; +} + +function onPullTouchStart(e: TouchEvent) { + if (tourStep !== null) return; + if (pullState === "refreshing") return; + if (getPullScrollTop() > 4) return; + _pullStartY = e.touches[0].clientY; + _pullTracking = true; +} + +function onPullTouchMove(e: TouchEvent) { + if (!_pullTracking || pullState === "refreshing") return; + const delta = e.touches[0].clientY - _pullStartY; + if (delta <= 0) { + _pullTracking = false; + pullState = "idle"; + pullDistance = 0; + return; + } + e.preventDefault(); + pullDistance = Math.min(delta * 0.55, PULL_MAX); + pullState = pullDistance >= PULL_THRESHOLD ? "triggered" : "pulling"; +} + +function onPullTouchEnd() { + if (!_pullTracking) return; + _pullTracking = false; + if (pullState === "triggered") { + pullState = "refreshing"; + pullDistance = PULL_THRESHOLD; + void refreshBindings().finally(() => { + pullState = "idle"; + pullDistance = 0; + }); + } else { + pullState = "idle"; + pullDistance = 0; + } +} + +function onPullTouchCancel() { + _pullTracking = false; + if (pullState !== "refreshing") { + pullState = "idle"; + pullDistance = 0; + } +} + +async function loadUserInfo(): Promise { + if (!globalState) return; + const [userInfo, fake, vaultData] = await Promise.all([ + globalState.userController.user, + globalState.userController.isFake, + globalState.vaultController.vault, + ]); + isFake = fake; + cachedIsFake = fake; + userData = { ...userInfo, isFake: fake }; + cachedUserData = userData; + if (vaultData?.ename) { + ename = vaultData.ename; + cachedEname = ename; + } +} + function openKycFlow() { kycOpen = true; } @@ -753,15 +829,7 @@ onMount(() => { profileCreationStatus = gs.vaultController.profileCreationStatus; - const userInfo = await gs.userController.user; - const fake = await gs.userController.isFake; - isFake = fake; - cachedIsFake = fake; - userData = { ...userInfo, isFake: fake }; - cachedUserData = userData; - const vaultData = await gs.vaultController.vault; - ename = vaultData?.ename; - cachedEname = ename; + await loadUserInfo(); void Promise.allSettled([ loadBindingDocuments(), @@ -807,6 +875,10 @@ onMount(() => { }; if (typeof document !== "undefined") { document.addEventListener("visibilitychange", onVisibility); + document.addEventListener("touchstart", onPullTouchStart, { passive: true }); + document.addEventListener("touchmove", onPullTouchMove, { passive: false }); + document.addEventListener("touchend", onPullTouchEnd, { passive: true }); + document.addEventListener("touchcancel", onPullTouchCancel, { passive: true }); } }); @@ -817,8 +889,12 @@ onDestroy(() => { if (bindingsRefreshInterval) { clearInterval(bindingsRefreshInterval); } - if (typeof document !== "undefined" && onVisibility) { - document.removeEventListener("visibilitychange", onVisibility); + if (typeof document !== "undefined") { + if (onVisibility) document.removeEventListener("visibilitychange", onVisibility); + document.removeEventListener("touchstart", onPullTouchStart); + document.removeEventListener("touchmove", onPullTouchMove); + document.removeEventListener("touchend", onPullTouchEnd); + document.removeEventListener("touchcancel", onPullTouchCancel); } unsubNotifications?.(); }); @@ -831,6 +907,7 @@ async function refreshBindings(): Promise { if (!globalState) return; try { await Promise.all([ + loadUserInfo(), loadBindingDocuments(), loadPersonalIntoStore(), ]); @@ -871,6 +948,37 @@ async function refreshBindings(): Promise { {:else} + + {#if pullState !== "idle"} + + {/if} +