From 0c36574ef6183f267a7a56903a3601a898881a50 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ren=C3=A9=20Aaron?= Date: Wed, 11 Sep 2024 15:13:44 +0200 Subject: [PATCH] feat: add infos about how to connect --- app/_layout.tsx | 2 + components/Icons.tsx | 4 + components/QRCodeScanner.tsx | 66 +++--- components/ui/dialog.tsx | 148 +++++++++++++ package.json | 2 + pages/settings/wallets/WalletConnection.tsx | 47 ++-- yarn.lock | 232 ++++++++++++++++++++ 7 files changed, 452 insertions(+), 49 deletions(-) create mode 100644 components/ui/dialog.tsx diff --git a/app/_layout.tsx b/app/_layout.tsx index 4a6de40..eef30ff 100644 --- a/app/_layout.tsx +++ b/app/_layout.tsx @@ -22,6 +22,7 @@ import * as Font from "expo-font"; import { useInfo } from "~/hooks/useInfo"; import { secureStorage } from "~/lib/secureStorage"; import { hasOnboardedKey } from "~/lib/state/appStore"; +import { PortalHost } from '@rn-primitives/portal'; const LIGHT_THEME: Theme = { dark: false, @@ -103,6 +104,7 @@ export default function RootLayout() { + diff --git a/components/Icons.tsx b/components/Icons.tsx index 2fe4bf0..2d07a13 100644 --- a/components/Icons.tsx +++ b/components/Icons.tsx @@ -34,6 +34,7 @@ import { CameraOff, Palette, Egg, + HelpCircle } from "lucide-react-native"; import { cssInterop } from "nativewind"; @@ -83,6 +84,8 @@ interopIcon(Power); interopIcon(CameraOff); interopIcon(Palette); interopIcon(Egg); +interopIcon(HelpCircle); + export { AlertCircle, @@ -119,4 +122,5 @@ export { Power, Palette, Egg, + HelpCircle, }; diff --git a/components/QRCodeScanner.tsx b/components/QRCodeScanner.tsx index bfa903f..07407f2 100644 --- a/components/QRCodeScanner.tsx +++ b/components/QRCodeScanner.tsx @@ -9,62 +9,58 @@ import { CameraOff } from "./Icons"; type QRCodeScannerProps = { onScanned: (data: string) => void; + scanning?: boolean; }; -function QRCodeScanner({ onScanned }: QRCodeScannerProps) { - const [isScanning, setScanning] = React.useState(false); +function QRCodeScanner({ onScanned, scanning = true }: QRCodeScannerProps) { const [isLoading, setLoading] = React.useState(false); const [permissionStatus, setPermissionStatus] = React.useState(PermissionStatus.UNDETERMINED); useEffect(() => { - // Add some timeout to allow the screen transition to finish before - // starting the camera to avoid stutters - setLoading(true); - window.setTimeout(async () => { - await scan(); - setLoading(false); - }, 200); - }, []); + if (scanning) { + // Add some timeout to allow the screen transition to finish before + // starting the camera to avoid stutters + setLoading(true); + window.setTimeout(async () => { + await requestCameraPermission(); + setLoading(false); + }, 200); + } + }, [scanning]); - async function scan() { + async function requestCameraPermission() { const { status } = await Camera.requestCameraPermissionsAsync(); setPermissionStatus(status); - setScanning(status === "granted"); } const handleScanned = (data: string) => { - setScanning((current) => { - if (current === true) { - console.log(`Bar code with data ${data} has been scanned!`); - onScanned(data); - return true; - } - return false; - }); + if (scanning) { + console.log(`Bar code with data ${data} has been scanned!`); + onScanned(data); + } }; return ( <> - {isLoading && ( + {isLoading || !scanning && ( )} - {!isLoading && <> - {!isScanning && permissionStatus === PermissionStatus.DENIED && - - - Camera Permission Denied - It seems you denied permissions to use your camera. You might need to go to your device settings to allow access to your camera again. - - } - {isScanning && ( - <> + {!isLoading && scanning && ( + <> + {permissionStatus === PermissionStatus.DENIED && ( + + + Camera Permission Denied + It seems you denied permissions to use your camera. You might need to go to your device settings to allow access to your camera again. + + )} + {permissionStatus === PermissionStatus.GRANTED && ( - - )} - - } + )} + + )} ); } diff --git a/components/ui/dialog.tsx b/components/ui/dialog.tsx new file mode 100644 index 0000000..228e371 --- /dev/null +++ b/components/ui/dialog.tsx @@ -0,0 +1,148 @@ +import * as DialogPrimitive from '@rn-primitives/dialog'; +import * as React from 'react'; +import { Platform, StyleSheet, View } from 'react-native'; +import Animated, { FadeIn, FadeOut } from 'react-native-reanimated'; +import { cn } from '~/lib/utils'; +import { X } from '../Icons'; + +const Dialog = DialogPrimitive.Root; + +const DialogTrigger = DialogPrimitive.Trigger; + +const DialogPortal = DialogPrimitive.Portal; + +const DialogClose = DialogPrimitive.Close; + +const DialogOverlayWeb = React.forwardRef< + React.ElementRef, + React.ComponentPropsWithoutRef +>(({ className, ...props }, ref) => { + const { open } = DialogPrimitive.useRootContext(); + return ( + + ); +}); + +DialogOverlayWeb.displayName = 'DialogOverlayWeb'; + +const DialogOverlayNative = React.forwardRef< + React.ElementRef, + React.ComponentPropsWithoutRef +>(({ className, children, ...props }, ref) => { + return ( + + + <>{children} + + + ); +}); + +DialogOverlayNative.displayName = 'DialogOverlayNative'; + +const DialogOverlay = Platform.select({ + web: DialogOverlayWeb, + default: DialogOverlayNative, +}); + +const DialogContent = React.forwardRef< + React.ElementRef, + React.ComponentPropsWithoutRef & { portalHost?: string } +>(({ className, children, portalHost, ...props }, ref) => { + const { open } = DialogPrimitive.useRootContext(); + return ( + + + + {children} + + + + + + + ); +}); +DialogContent.displayName = DialogPrimitive.Content.displayName; + +const DialogHeader = ({ className, ...props }: React.ComponentPropsWithoutRef) => ( + +); +DialogHeader.displayName = 'DialogHeader'; + +const DialogFooter = ({ className, ...props }: React.ComponentPropsWithoutRef) => ( + +); +DialogFooter.displayName = 'DialogFooter'; + +const DialogTitle = React.forwardRef< + React.ElementRef, + React.ComponentPropsWithoutRef +>(({ className, ...props }, ref) => ( + +)); +DialogTitle.displayName = DialogPrimitive.Title.displayName; + +const DialogDescription = React.forwardRef< + React.ElementRef, + React.ComponentPropsWithoutRef +>(({ className, ...props }, ref) => ( + +)); +DialogDescription.displayName = DialogPrimitive.Description.displayName; + +export { + Dialog, + DialogClose, + DialogContent, + DialogDescription, + DialogFooter, + DialogHeader, + DialogOverlay, + DialogPortal, + DialogTitle, + DialogTrigger, +}; diff --git a/package.json b/package.json index ea8b1d3..7358800 100644 --- a/package.json +++ b/package.json @@ -19,6 +19,8 @@ "@getalby/lightning-tools": "^5.0.3", "@getalby/sdk": "^3.7.0", "@react-native-async-storage/async-storage": "1.23.1", + "@rn-primitives/dialog": "^1.0.3", + "@rn-primitives/portal": "^1.0.3", "bech32": "^2.0.0", "buffer": "^6.0.3", "class-variance-authority": "^0.7.0", diff --git a/pages/settings/wallets/WalletConnection.tsx b/pages/settings/wallets/WalletConnection.tsx index 4f5bf03..0cf3262 100644 --- a/pages/settings/wallets/WalletConnection.tsx +++ b/pages/settings/wallets/WalletConnection.tsx @@ -1,10 +1,9 @@ import { Pressable, Text, View } from "react-native"; -import React from "react"; +import React, { useEffect } from "react"; import * as Clipboard from "expo-clipboard"; import { nwc } from "@getalby/sdk"; import { ClipboardPaste, X } from "~/components/Icons"; import { useAppStore } from "lib/state/appStore"; -import { Camera } from "expo-camera/legacy"; // TODO: check if Android camera detach bug is fixed and update camera import { router } from "expo-router"; import { Button } from "~/components/ui/button"; import { useInfo } from "~/hooks/useInfo"; @@ -15,29 +14,27 @@ import { Nip47Capability } from "@getalby/sdk/dist/NWCClient"; import Loading from "~/components/Loading"; import QRCodeScanner from "~/components/QRCodeScanner"; import Screen from "~/components/Screen"; +import { Dialog, DialogContent, DialogHeader, DialogTitle, DialogFooter, DialogClose } from "~/components/ui/dialog"; export function WalletConnection() { const hasConnection = useAppStore((store) => !!store.nwcClient); const walletIdWithConnection = useAppStore((store) => store.wallets.findIndex((wallet) => wallet.nostrWalletConnectUrl), ); - const [isScanning, setScanning] = React.useState(false); - const [isConnecting, setConnecting] = React.useState(false); + const isFirstConnection = useAppStore((store) => !store.wallets.length) const { data: walletInfo } = useInfo(); const { data: balance } = useBalance(); - - async function scan() { - const { status } = await Camera.requestCameraPermissionsAsync(); - setScanning(status === "granted"); - } + const [isScanning, setScanning] = React.useState(false); + const [isConnecting, setConnecting] = React.useState(false); + const [dialogOpen, setDialogOpen] = React.useState(isFirstConnection); const handleScanned = (data: string) => { return connect(data); }; - React.useEffect(() => { - scan(); - }, []); + useEffect(() => { + setScanning(!dialogOpen); + }, [dialogOpen]); async function paste() { let nostrWalletConnectUrl; @@ -127,7 +124,7 @@ export function WalletConnection() { variant="destructive" onPress={() => { useAppStore.getState().removeNostrWalletConnectUrl(); - scan(); + setScanning(true); }} > Disconnect Wallet @@ -144,9 +141,31 @@ export function WalletConnection() { )} + + + + + Connect Your Wallet + + Follow these steps to connect Alby Go to your Hub: + 1. Open your Alby Hub + 2. Go to App Store » Alby Go + 3. Scan the QR code with this app + + + + + + + + + + {!isConnecting && ( <> - +