rvv001 - Issue 0085 - URI support implemented in GUI

- Support to generate and display a QR Code containing
		       a  ZIP-321 formatted string
This commit is contained in:
Rene V. Vergara 2025-01-15 22:05:36 -05:00
parent 3da6a57d50
commit ee71b7acbb
3 changed files with 157 additions and 68 deletions

View file

@ -22,11 +22,15 @@ import Control.Monad.Logger
) )
import Data.Aeson import Data.Aeson
import qualified Data.ByteString as BS import qualified Data.ByteString as BS
import qualified Data.ByteString.Lazy as BL
import Data.HexString (toText) import Data.HexString (toText)
import Data.Maybe (fromMaybe, isJust, isNothing) import Data.Maybe (fromMaybe, isJust, isNothing)
import Data.Scientific (Scientific, fromFloatDigits) import Data.Scientific (Scientific, fromFloatDigits)
import Data.Text (Text)
import qualified Data.Text as T import qualified Data.Text as T
import qualified Data.Text.Encoding as E import qualified Data.Text.Encoding as E
import qualified Data.Text.Lazy as TL
import qualified Data.Text.Lazy.Encoding as TLE
import Data.Time.Clock.POSIX (posixSecondsToUTCTime) import Data.Time.Clock.POSIX (posixSecondsToUTCTime)
import Database.Esqueleto.Experimental (ConnectionPool, fromSqlKey) import Database.Esqueleto.Experimental (ConnectionPool, fromSqlKey)
import Database.Persist import Database.Persist
@ -170,7 +174,7 @@ data AppEvent
| ClosePaymentURIForm | ClosePaymentURIForm
| PrepareURIString | PrepareURIString
| CloseShowURIOverlay | CloseShowURIOverlay
| ShowURIOverlay !T.Text | ShowURIOverlay !(Maybe URIQrCode) !T.Text
| CopyURIString !T.Text | CopyURIString !T.Text
| DisplayPayUsingURI | DisplayPayUsingURI
| ClosePayUsingURI | ClosePayUsingURI
@ -247,6 +251,7 @@ data AppModel = AppModel
, _usepmtURIOverlay :: !Bool , _usepmtURIOverlay :: !Bool
, _uriString :: !T.Text , _uriString :: !T.Text
, _uriAddr :: !T.Text , _uriAddr :: !T.Text
, _uriQRImage :: !(Maybe URIQrCode)
} deriving (Eq, Show) } deriving (Eq, Show)
makeLenses ''AppModel makeLenses ''AppModel
@ -260,6 +265,18 @@ remixHourglassFill = toGlyph 0xF338
remixIcon :: T.Text -> WidgetNode s e remixIcon :: T.Text -> WidgetNode s e
remixIcon i = label i `styleBasic` [textFont "Remix", textMiddle] remixIcon i = label i `styleBasic` [textFont "Remix", textMiddle]
getURIQRWidth :: Maybe URIQrCode -> Int
getURIQRWidth qr =
case qr of
Nothing -> 0
Just qr -> round (uriWidth qr)
getURIQRHeight :: Maybe URIQrCode -> Int
getURIQRHeight qr =
case qr of
Nothing -> 0
Just qr -> round (uriHeight qr)
buildUI :: buildUI ::
WidgetEnv AppModel AppEvent -> AppModel -> WidgetNode AppModel AppEvent WidgetEnv AppModel AppEvent -> AppModel -> WidgetNode AppModel AppEvent
buildUI wenv model = widgetTree buildUI wenv model = widgetTree
@ -1251,8 +1268,14 @@ buildUI wenv model = widgetTree
[bgColor (white & L.a .~ 0.5)] [bgColor (white & L.a .~ 0.5)]
-- --
paymentURIOverlay = paymentURIOverlay =
alert ClosePaymentURIForm $ box
vstack (vstack
[ filler
, hstack
[ filler
, box_
[]
(vstack
[ box_ [ box_
[] []
(label "Create a Payment URI" `styleBasic` (label "Create a Payment URI" `styleBasic`
@ -1288,17 +1311,31 @@ buildUI wenv model = widgetTree
[ filler [ filler
, mainButton "Create URI" PrepareURIString `nodeEnabled` , mainButton "Create URI" PrepareURIString `nodeEnabled`
(model ^. sendAmount > 0.0) (model ^. sendAmount > 0.0)
, spacer
, button "Cancel" ClosePaymentURIForm
, filler , filler
] ]
] `styleBasic` [bgColor (white & L.a .~ 0.5)] ]) `styleBasic`
[radius 4, border 2 btnColor, bgColor white, padding 4]
, filler
]
, filler
]) `styleBasic`
[bgColor (white & L.a .~ 0.5)]
-- --
showURIOverlay = showURIOverlay =
alert CloseShowURIOverlay $ box
vstack (vstack
[ box_ [ filler
, hstack
[ filler
, box_
[] []
(vstack
[ box_
[alignMiddle]
(label "Payment URI" `styleBasic` (label "Payment URI" `styleBasic`
[textFont "Bold", textColor white, textSize 12, padding 3]) `styleBasic` [textFont "Bold", textColor white, textSize 11, padding 3]) `styleBasic`
[bgColor btnColor, radius 2, padding 3] [bgColor btnColor, radius 2, padding 3]
, spacer , spacer
, hstack , hstack
@ -1306,11 +1343,37 @@ buildUI wenv model = widgetTree
, spacer , spacer
, hstack , hstack
[ filler [ filler
, button "Copy to Clipboard" $ , box_
CopyURIString (model ^. uriString) [alignMiddle]
-- (image_ (T.pack ((model ^. home) </> "Zenith/assets/qr_image.png")) [fitFill])
(case model ^. uriQRImage of
Just img -> imageMem_ "URIQRCode" (uriBytes img) (Size (uriWidth img) (uriHeight img) ) [fitWidth]
Nothing -> image_
(T.pack $ (model ^. home) </> "Zenith/assets/cracked_qr.png")
[fitHeight] )
`styleBasic` [ bgColor white
, height 120
, width 120
]
, filler , filler
] ]
, spacer
, hstack
[ filler
, button "Copy to Clipboard" $
CopyURIString (model ^. uriString)
, spacer
, button "Cancel" CloseShowURIOverlay
, filler
] ]
]) `styleBasic`
[radius 4, border 2 btnColor, bgColor white, padding 4]
, filler
]
, filler
] ) `styleBasic`
[bgColor (white & L.a .~ 0.5)]
--
pmtUsingURIOverlay = pmtUsingURIOverlay =
box box
(vstack (vstack
@ -1848,13 +1911,14 @@ handleEvent wenv node model evt =
-- Generate URI -- Generate URI
-- --
PrepareURIString -> [ Task $ genURIString (model ^. uriAddr) (model ^. sendAmount) (model ^. sendMemo) ] PrepareURIString -> [ Task $ genURIString (model ^. uriAddr) (model ^. sendAmount) (model ^. sendMemo) ]
ShowURIOverlay uStr -> ShowURIOverlay qr uStr ->
[ Model $ [ Model $
model & uriString .~ uStr model & uriString .~ uStr
& uriQRImage .~ qr
& paymentURIDisplay .~ False & paymentURIDisplay .~ False
& showURIDisplay .~ True & showURIDisplay .~ True
] ]
CloseShowURIOverlay -> [ Model $ model & showURIDisplay .~ False & uriString .~ "" ] CloseShowURIOverlay -> [ Model $ model & showURIDisplay .~ False & uriString .~ "" & uriQRImage .~ Nothing]
-- --
-- Display Pay using URI Form -- Display Pay using URI Form
-- --
@ -2110,6 +2174,23 @@ handleEvent wenv node model evt =
t_rec =<< (isValidUnifiedAddress . E.encodeUtf8 . getUA . walletAddressUAddress . entityVal) =<< a t_rec =<< (isValidUnifiedAddress . E.encodeUtf8 . getUA . walletAddressUAddress . entityVal) =<< a
return $ DisplayPaymentURIForm ua return $ DisplayPaymentURIForm ua
-- --
-- Generate a QR code for a String and save it as an PNG image
--
genURIStringQR :: Int -> T.Text -> Maybe URIQrCode
genURIStringQR scaleFactor uriStr = do
let qrOptions = defaultQRCodeOptions L
case encodeText qrOptions Utf8WithoutECI uriStr of
Nothing -> Nothing
Just qrCode -> do
let qri = promoteImage (toImage 4 scaleFactor qrCode)
let qrw = fromIntegral $ imageWidth qri
let qrh = fromIntegral $ imageHeight qri
let qrb = BS.pack $
pixelFold (\bs _ _ (PixelRGBA8 i j k l) -> bs <> [i, j, k, l])
[]
qri
Just URIQrCode { uriBytes=qrb, uriWidth=qrw, uriHeight=qrh }
--
-- Gen URI String -- Gen URI String
-- --
genURIString :: T.Text -> Float -> T.Text -> IO AppEvent genURIString :: T.Text -> Float -> T.Text -> IO AppEvent
@ -2118,7 +2199,7 @@ handleEvent wenv node model evt =
"" -> Nothing "" -> Nothing
_ -> Just (T.unpack mMemo) _ -> Just (T.unpack mMemo)
let uriSt = createZip321 (T.unpack addr) (Just (realToFrac mAmt)) mM let uriSt = createZip321 (T.unpack addr) (Just (realToFrac mAmt)) mM
return $ ShowURIOverlay (T.pack uriSt) return $ ShowURIOverlay (genURIStringQR 3 (T.pack uriSt)) (T.pack uriSt)
scanZebra :: scanZebra ::
T.Text T.Text
@ -2475,6 +2556,7 @@ runZenithGUI config = do
False False
"" ""
"" ""
Nothing
startApp model handleEvent buildUI (params hD) startApp model handleEvent buildUI (params hD)
Left _e -> print "Zebra not available" Left _e -> print "Zebra not available"
where where

View file

@ -517,3 +517,11 @@ data ZcashPaymentURI = ZcashPaymentURI
, uriLabel :: Maybe String , uriLabel :: Maybe String
, uriMessage :: Maybe String , uriMessage :: Maybe String
} deriving (Show, Eq) } deriving (Show, Eq)
-- | Define a data structure for the URI QR image
data URIQrCode = URIQrCode
{
uriBytes :: BS.ByteString -- Image as ByteString
, uriWidth :: Double -- Number of columns in QR Image
, uriHeight :: Double -- Number of rows in a QR Image
} deriving (Show, Eq)

View file

@ -17,7 +17,6 @@ import Data.Functor (void)
import Data.Maybe import Data.Maybe
import Data.Ord (clamp) import Data.Ord (clamp)
import Data.Scientific (Scientific(..), scientific, Scientific, toRealFloat) import Data.Scientific (Scientific(..), scientific, Scientific, toRealFloat)
--import Data.Scientific (Scientific, toRealFloat)
import qualified Data.Text as T import qualified Data.Text as T
import qualified Data.Text.Encoding as E import qualified Data.Text.Encoding as E
import qualified Data.Text.Encoding as TE import qualified Data.Text.Encoding as TE