From e589fd26642a1a92c3e39a939e57c1dd52463c3b Mon Sep 17 00:00:00 2001 From: im-adithya Date: Fri, 7 Feb 2025 11:12:55 +0530 Subject: [PATCH 1/4] feat: swipe to change wallets --- app/_layout.tsx | 27 ++++--- package.json | 9 ++- pages/Home.tsx | 195 +++++++++++++++++++++++++++--------------------- yarn.lock | 30 +++++++- 4 files changed, 160 insertions(+), 101 deletions(-) diff --git a/app/_layout.tsx b/app/_layout.tsx index d53eb0d1..37040109 100644 --- a/app/_layout.tsx +++ b/app/_layout.tsx @@ -11,6 +11,7 @@ import * as SplashScreen from "expo-splash-screen"; import { StatusBar } from "expo-status-bar"; import { swrConfiguration } from "lib/swr"; import * as React from "react"; +import { GestureHandlerRootView } from "react-native-gesture-handler"; import { SafeAreaView } from "react-native-safe-area-context"; import Toast from "react-native-toast-message"; import { SWRConfig } from "swr"; @@ -120,18 +121,20 @@ export default function RootLayout() { className="w-full h-full bg-background" edges={["left", "right", "bottom"]} > - - - - - - - + + + + + + + + + diff --git a/package.json b/package.json index b48c830d..4a88380a 100644 --- a/package.json +++ b/package.json @@ -29,9 +29,9 @@ "dependencies": { "@alevy97/react-native-userdefaults": "^0.2.2", "@getalby/expo-shared-preferences": "^0.0.1", - "@noble/curves": "^1.6.0", "@getalby/lightning-tools": "^5.1.2", "@getalby/sdk": "^3.9.0", + "@noble/curves": "^1.6.0", "@popicons/react-native": "^0.0.20", "@react-native-async-storage/async-storage": "1.23.1", "@rn-primitives/dialog": "^1.0.3", @@ -42,17 +42,17 @@ "class-variance-authority": "^0.7.0", "clsx": "^2.1.1", "dayjs": "^1.11.10", - "expo-device": "~6.0.2", - "expo-notification-service-extension-plugin": "^1.0.1", - "expo-notifications": "~0.29.11", "expo": "~52.0.20", "expo-camera": "~16.0.10", "expo-clipboard": "~7.0.0", "expo-constants": "~17.0.3", + "expo-device": "~6.0.2", "expo-font": "~13.0.2", "expo-linear-gradient": "~14.0.1", "expo-linking": "~7.0.3", "expo-local-authentication": "~15.0.1", + "expo-notification-service-extension-plugin": "^1.0.1", + "expo-notifications": "~0.29.11", "expo-router": "~4.0.15", "expo-secure-store": "~14.0.0", "expo-splash-screen": "^0.29.18", @@ -64,6 +64,7 @@ "react": "18.3.1", "react-dom": "18.3.1", "react-native": "0.76.5", + "react-native-gesture-handler": "^2.23.0", "react-native-get-random-values": "^1.9.0", "react-native-qrcode-svg": "^6.3.1", "react-native-reanimated": "~3.16.1", diff --git a/pages/Home.tsx b/pages/Home.tsx index e7679e1c..132c1d4f 100644 --- a/pages/Home.tsx +++ b/pages/Home.tsx @@ -16,6 +16,8 @@ import { ChevronUpIcon, SettingsIcon } from "~/components/Icons"; import { Text } from "~/components/ui/text"; import { LinearGradient } from "expo-linear-gradient"; +import { Gesture, GestureDetector } from "react-native-gesture-handler"; +import { runOnJS } from "react-native-reanimated"; import { SvgProps } from "react-native-svg"; import AlbyBanner from "~/components/AlbyBanner"; import LargeArrowDown from "~/components/icons/LargeArrowDown"; @@ -60,6 +62,29 @@ export function Home() { } } + function handleSwipe(direction: "left" | "right") { + if (!wallets.length) { + return; + } + let newId = selectedWalletId; + if (direction === "left") { + newId = (selectedWalletId + 1) % wallets.length; + } else { + newId = (selectedWalletId - 1 + wallets.length) % wallets.length; + } + useAppStore.getState().setSelectedWalletId(newId); + refreshBalance(); + } + + const swipeGesture = Gesture.Pan().onEnd((evt) => { + const threshold = 50; + if (evt.translationX < -threshold) { + runOnJS(handleSwipe)("left"); + } else if (evt.translationX > threshold) { + runOnJS(handleSwipe)("right"); + } + }); + return ( <> )} /> - - - } - showsVerticalScrollIndicator={false} - contentContainerClassName="flex-1" - > - - - {wallets.length > 1 && ( - { - router.push("/settings/wallets"); - }} - > - + + + } + showsVerticalScrollIndicator={false} + contentContainerClassName="flex-1" + > + + + {wallets.length > 1 && ( + { + router.push("/settings/wallets"); + }} > - {wallets[selectedWalletId].name || DEFAULT_WALLET_NAME} - - - )} - - {balance && !refreshingBalance ? ( - <> - + + {wallets[selectedWalletId].name || DEFAULT_WALLET_NAME} + + + )} + + {balance && !refreshingBalance ? ( + <> + + {balanceDisplayMode === "sats" && + new Intl.NumberFormat().format( + Math.floor(balance.balance / 1000), + )} + {balanceDisplayMode === "fiat" && + getFiatAmount && + getFiatAmount(Math.floor(balance.balance / 1000))} + {balanceDisplayMode === "hidden" && "****"} + + {balanceDisplayMode === "sats" && ( + + sats + + )} + + ) : ( + + )} + + + {balance && !refreshingBalance ? ( + {balanceDisplayMode === "sats" && - new Intl.NumberFormat().format( - Math.floor(balance.balance / 1000), - )} - {balanceDisplayMode === "fiat" && getFiatAmount && getFiatAmount(Math.floor(balance.balance / 1000))} - {balanceDisplayMode === "hidden" && "****"} + {balanceDisplayMode === "fiat" && + new Intl.NumberFormat().format( + Math.floor(balance.balance / 1000), + ) + " sats"} - {balanceDisplayMode === "sats" && ( - - sats - - )} - - ) : ( - - )} - - - {balance && !refreshingBalance ? ( - - {balanceDisplayMode === "sats" && - getFiatAmount && - getFiatAmount(Math.floor(balance.balance / 1000))} - {balanceDisplayMode === "fiat" && - new Intl.NumberFormat().format( - Math.floor(balance.balance / 1000), - ) + " sats"} - - ) : ( - - )} - - - {new Date().getDate() === 21 && } + ) : ( + + )} + + + {new Date().getDate() === 21 && } + + + + + + + + + + + + - - - - - - - - - - - - + ); } diff --git a/yarn.lock b/yarn.lock index 91ad886c..7ee59c47 100644 --- a/yarn.lock +++ b/yarn.lock @@ -842,6 +842,13 @@ resolved "https://registry.yarnpkg.com/@bcoe/v8-coverage/-/v8-coverage-0.2.3.tgz#75a2e8b51cb758a7553d6804a5932d7aace75c39" integrity sha512-0hYQ8SB4Db5zvZB4axdMHGwEaQjkZzFjQiN9LVYvIFB2nSUHW9tYpxWriPrWDASIxiaXax83REcLxuSdnGPZtw== +"@egjs/hammerjs@^2.0.17": + version "2.0.17" + resolved "https://registry.yarnpkg.com/@egjs/hammerjs/-/hammerjs-2.0.17.tgz#5dc02af75a6a06e4c2db0202cae38c9263895124" + integrity sha512-XQsZgjm2EcVUiZQf11UBJQfmZeEmOW8DpI1gsFeln6w0ae0ii4dMQEQ0kjl6DspdWX1aGY1/loyXnP0JS06e/A== + dependencies: + "@types/hammerjs" "^2.0.36" + "@eslint-community/eslint-utils@^4.2.0", "@eslint-community/eslint-utils@^4.4.0": version "4.4.1" resolved "https://registry.yarnpkg.com/@eslint-community/eslint-utils/-/eslint-utils-4.4.1.tgz#d1145bf2c20132d6400495d6df4bf59362fd9d56" @@ -2322,6 +2329,11 @@ dependencies: "@types/node" "*" +"@types/hammerjs@^2.0.36": + version "2.0.46" + resolved "https://registry.yarnpkg.com/@types/hammerjs/-/hammerjs-2.0.46.tgz#381daaca1360ff8a7c8dff63f32e69745b9fb1e1" + integrity sha512-ynRvcq6wvqexJ9brDMS4BnBLzmr0e14d6ZJTEShTBWKymQiHwlAyGu0ZPEFI2Fh1U53F7tN9ufClWM5KvqkKOw== + "@types/istanbul-lib-coverage@*", "@types/istanbul-lib-coverage@^2.0.0", "@types/istanbul-lib-coverage@^2.0.1": version "2.0.6" resolved "https://registry.yarnpkg.com/@types/istanbul-lib-coverage/-/istanbul-lib-coverage-2.0.6.tgz#7739c232a1fee9b4d3ce8985f314c0c6d33549d7" @@ -5299,6 +5311,13 @@ hermes-parser@0.25.1: dependencies: hermes-estree "0.25.1" +hoist-non-react-statics@^3.3.0: + version "3.3.2" + resolved "https://registry.yarnpkg.com/hoist-non-react-statics/-/hoist-non-react-statics-3.3.2.tgz#ece0acaf71d62c2969c2ec59feff42a4b1a85b45" + integrity sha512-/gGivxi8JPKWNm/W0jSmzcMPpfpPLc3dY/6GxhX2hQ9iGj3aDfklV4ET7NjKpSinLpJ5vafa9iiGIEZg10SfBw== + dependencies: + react-is "^16.7.0" + hosted-git-info@^7.0.0: version "7.0.2" resolved "https://registry.yarnpkg.com/hosted-git-info/-/hosted-git-info-7.0.2.tgz#9b751acac097757667f30114607ef7b661ff4f17" @@ -8005,7 +8024,7 @@ react-helmet-async@^1.3.0: resolved "https://registry.yarnpkg.com/react-is/-/react-is-18.3.1.tgz#e83557dc12eae63a99e003a46388b1dcbb44db7e" integrity sha512-/LLMVyas0ljjAtoYiPqYiL8VWXzUUdThrmU5+n20DZv+a+ClRoevUzw5JxU+Ieh5/c87ytoTBV9G1FiKfNJdmg== -react-is@^16.13.1: +react-is@^16.13.1, react-is@^16.7.0: version "16.13.1" resolved "https://registry.yarnpkg.com/react-is/-/react-is-16.13.1.tgz#789729a4dc36de2999dc156dd6c1d9c18cea56a4" integrity sha512-24e6ynE2H+OKt4kqsOvNd8kBpV65zoxbA4BVsEOB3ARVWQki/DHzaUoC5KuON/BiccDaCCTZBuOcfZs70kR8bQ== @@ -8022,6 +8041,15 @@ react-native-css-interop@0.1.22: lightningcss "^1.27.0" semver "^7.6.3" +react-native-gesture-handler@^2.23.0: + version "2.23.0" + resolved "https://registry.yarnpkg.com/react-native-gesture-handler/-/react-native-gesture-handler-2.23.0.tgz#f0e77bc6bce94a976ae7917abc748b7b4ce6a490" + integrity sha512-xtkdIU4S4uc4J2WO4hy7AXxD/1M8Be2yOrLdPTuWKAOF3KyL0D0xSdvuaWhI+GdZCNQQisj9kvbnMQGGb9XZNQ== + dependencies: + "@egjs/hammerjs" "^2.0.17" + hoist-non-react-statics "^3.3.0" + invariant "^2.2.4" + react-native-get-random-values@^1.9.0: version "1.11.0" resolved "https://registry.yarnpkg.com/react-native-get-random-values/-/react-native-get-random-values-1.11.0.tgz#1ca70d1271f4b08af92958803b89dccbda78728d" From 227caa515dcb4c32baf144f560325d6c912864e2 Mon Sep 17 00:00:00 2001 From: im-adithya Date: Thu, 20 Feb 2025 13:46:01 +0530 Subject: [PATCH 2/4] chore: do not force balance refresh --- pages/Home.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pages/Home.tsx b/pages/Home.tsx index 2d2f12b2..af4ef2fb 100644 --- a/pages/Home.tsx +++ b/pages/Home.tsx @@ -72,8 +72,8 @@ export function Home() { } else { newId = (selectedWalletId - 1 + wallets.length) % wallets.length; } + // SWR detects the key change and updates the balance useAppStore.getState().setSelectedWalletId(newId); - refreshBalance(); } const swipeGesture = Gesture.Pan().onEnd((evt) => { From f2cc4bd62a0cc6cc1d5cd293668786aa7dc07358 Mon Sep 17 00:00:00 2001 From: im-adithya Date: Fri, 21 Feb 2025 17:34:48 +0530 Subject: [PATCH 3/4] chore: add gesturewrapper with wiggle animation --- components/GestureWrapper.tsx | 59 +++++++++++++++++++++++++ pages/Home.tsx | 81 ++++++++++++++++------------------- 2 files changed, 96 insertions(+), 44 deletions(-) create mode 100644 components/GestureWrapper.tsx diff --git a/components/GestureWrapper.tsx b/components/GestureWrapper.tsx new file mode 100644 index 00000000..4f0d816b --- /dev/null +++ b/components/GestureWrapper.tsx @@ -0,0 +1,59 @@ +import { useFocusEffect } from "expo-router"; +import React from "react"; +import { Gesture, GestureDetector } from "react-native-gesture-handler"; +import Animated, { + runOnJS, + useAnimatedStyle, + useSharedValue, + withDelay, + withSequence, + withTiming, +} from "react-native-reanimated"; + +interface GestureWrapperProps { + children: React.ReactNode; + onSwipe: (direction: "left" | "right") => void; + shouldWiggle?: boolean; +} + +export function GestureWrapper({ + children, + onSwipe, + shouldWiggle, +}: GestureWrapperProps) { + const wiggle = useSharedValue(0); + + useFocusEffect( + React.useCallback(() => { + if (shouldWiggle) { + wiggle.value = withDelay( + 500, + withSequence( + withTiming(-10, { duration: 150 }), + withTiming(10, { duration: 300 }), + withTiming(0, { duration: 150 }), + ), + ); + } + }, [shouldWiggle, wiggle]), + ); + + const gesture = Gesture.Pan().onEnd((evt) => { + const threshold = 50; + if (evt.translationX < -threshold) { + runOnJS(onSwipe)("left"); + } else if (evt.translationX > threshold) { + runOnJS(onSwipe)("right"); + } + }); + + const animatedStyle = useAnimatedStyle(() => ({ + transform: [{ translateX: wiggle.value }], + })); + + return ( + + {children} + + ); +} diff --git a/pages/Home.tsx b/pages/Home.tsx index af4ef2fb..c2dafe7f 100644 --- a/pages/Home.tsx +++ b/pages/Home.tsx @@ -16,10 +16,9 @@ import { ChevronUpIcon, SettingsIcon } from "~/components/Icons"; import { Text } from "~/components/ui/text"; import { LinearGradient } from "expo-linear-gradient"; -import { Gesture, GestureDetector } from "react-native-gesture-handler"; -import { runOnJS } from "react-native-reanimated"; import { SvgProps } from "react-native-svg"; import AlbyBanner from "~/components/AlbyBanner"; +import { GestureWrapper } from "~/components/GestureWrapper"; import LargeArrowDown from "~/components/icons/LargeArrowDown"; import LargeArrowUp from "~/components/icons/LargeArrowUp"; import Screen from "~/components/Screen"; @@ -76,15 +75,6 @@ export function Home() { useAppStore.getState().setSelectedWalletId(newId); } - const swipeGesture = Gesture.Pan().onEnd((evt) => { - const threshold = 50; - if (evt.translationX < -threshold) { - runOnJS(handleSwipe)("left"); - } else if (evt.translationX > threshold) { - runOnJS(handleSwipe)("right"); - } - }); - return ( <> )} /> - - - - } - showsVerticalScrollIndicator={false} - contentContainerClassName="flex-1" - > - + + + } + showsVerticalScrollIndicator={false} + contentContainerClassName="flex-1" + > + + 1} + > )} - + {balance && !refreshingBalance ? ( <> @@ -177,26 +170,26 @@ export function Home() { )} - {new Date().getDate() === 21 && } - - - - - - - - - - - - + + {new Date().getDate() === 21 && } + + + + + + + + + + + - + ); } From ad526527e654799c95dcbf221e246a3170753eb4 Mon Sep 17 00:00:00 2001 From: im-adithya Date: Tue, 25 Feb 2025 14:53:33 +0530 Subject: [PATCH 4/4] chore: use expo compatible version --- package.json | 9 ++++++++- yarn.lock | 9 +++++---- 2 files changed, 13 insertions(+), 5 deletions(-) diff --git a/package.json b/package.json index 2bb13855..bbfefb42 100644 --- a/package.json +++ b/package.json @@ -64,7 +64,7 @@ "react": "18.3.1", "react-dom": "18.3.1", "react-native": "0.76.7", - "react-native-gesture-handler": "^2.23.0", + "react-native-gesture-handler": "~2.20.2", "react-native-get-random-values": "^1.9.0", "react-native-qrcode-svg": "^6.3.1", "react-native-reanimated": "~3.16.1", @@ -105,5 +105,12 @@ "transformIgnorePatterns": [ "node_modules/(?!(?:.pnpm/)?((jest-)?react-native|@react-native(-community)?|expo(nent)?|@expo(nent)?/.*|@expo-google-fonts/.*|react-navigation|@react-navigation/.*|@sentry/react-native|native-base|react-native-svg))" ] + }, + "expo": { + "doctor": { + "reactNativeDirectoryCheck": { + "listUnknownPackages": false + } + } } } diff --git a/yarn.lock b/yarn.lock index c4ffc42f..81f7828e 100644 --- a/yarn.lock +++ b/yarn.lock @@ -7973,14 +7973,15 @@ react-native-css-interop@0.1.22: lightningcss "^1.27.0" semver "^7.6.3" -react-native-gesture-handler@^2.23.0: - version "2.23.0" - resolved "https://registry.yarnpkg.com/react-native-gesture-handler/-/react-native-gesture-handler-2.23.0.tgz#f0e77bc6bce94a976ae7917abc748b7b4ce6a490" - integrity sha512-xtkdIU4S4uc4J2WO4hy7AXxD/1M8Be2yOrLdPTuWKAOF3KyL0D0xSdvuaWhI+GdZCNQQisj9kvbnMQGGb9XZNQ== +react-native-gesture-handler@~2.20.2: + version "2.20.2" + resolved "https://registry.yarnpkg.com/react-native-gesture-handler/-/react-native-gesture-handler-2.20.2.tgz#73844c8e9c417459c2f2981bc4d8f66ba8a5ee66" + integrity sha512-HqzFpFczV4qCnwKlvSAvpzEXisL+Z9fsR08YV5LfJDkzuArMhBu2sOoSPUF/K62PCoAb+ObGlTC83TKHfUd0vg== dependencies: "@egjs/hammerjs" "^2.0.17" hoist-non-react-statics "^3.3.0" invariant "^2.2.4" + prop-types "^15.7.2" react-native-get-random-values@^1.9.0: version "1.11.0"