From 0b8db145fb9534b5d18d92c9c61164e9526ff68e Mon Sep 17 00:00:00 2001 From: Philemon Ukane Date: Wed, 17 Jan 2024 08:00:55 +0100 Subject: [PATCH] add export feature on desktop Signed-off-by: Philemon Ukane --- libwallet/txhelper/helper.go | 15 ++++ ui/page/transaction/transactions_page.go | 96 +++++++++++++++++++++++- ui/values/const.go | 1 + ui/values/localizable/en.go | 6 ++ ui/values/strings.go | 6 ++ 5 files changed, 123 insertions(+), 1 deletion(-) diff --git a/libwallet/txhelper/helper.go b/libwallet/txhelper/helper.go index 3243e1803..92b695741 100644 --- a/libwallet/txhelper/helper.go +++ b/libwallet/txhelper/helper.go @@ -1,9 +1,11 @@ package txhelper import ( + "fmt" "math" "decred.org/dcrwallet/v3/wallet" + "github.com/crypto-power/cryptopower/ui/values" "github.com/decred/dcrd/dcrutil/v4" "github.com/decred/dcrd/wire" "github.com/decred/dcrdata/v8/txhelpers" @@ -54,3 +56,16 @@ func FormatTransactionType(txType wallet.TransactionType) string { return TxTypeRegular } } + +func TxDirectionString(direction int32) string { + switch direction { + case TxDirectionReceived: + return values.String(values.StrReceived) + case TxDirectionSent: + return values.String(values.StrSent) + case TxDirectionTransferred: + return values.String(values.StrTransferred) + default: + return fmt.Sprintf("%d", direction) + } +} diff --git a/ui/page/transaction/transactions_page.go b/ui/page/transaction/transactions_page.go index 4eaa35bf6..eedefc5ae 100644 --- a/ui/page/transaction/transactions_page.go +++ b/ui/page/transaction/transactions_page.go @@ -1,9 +1,15 @@ package transaction import ( + "encoding/csv" "fmt" + "math" + "os" + "path/filepath" + "runtime" "sort" "strings" + "time" "gioui.org/font" "gioui.org/layout" @@ -12,9 +18,11 @@ import ( "github.com/crypto-power/cryptopower/app" sharedW "github.com/crypto-power/cryptopower/libwallet/assets/wallet" + "github.com/crypto-power/cryptopower/libwallet/txhelper" "github.com/crypto-power/cryptopower/libwallet/utils" "github.com/crypto-power/cryptopower/ui/cryptomaterial" "github.com/crypto-power/cryptopower/ui/load" + "github.com/crypto-power/cryptopower/ui/modal" "github.com/crypto-power/cryptopower/ui/page/components" "github.com/crypto-power/cryptopower/ui/values" ) @@ -403,6 +411,10 @@ func (pg *TransactionsPage) leftDropdown(gtx C) D { }) }), layout.Rigid(func(gtx C) D { + // TODO: Enable on mobile + if pg.IsMobileView() { + return D{} + } return pg.buttonWrap(gtx, pg.exportBtn, pg.Theme.Icons.ShareIcon, values.String(values.StrExport)) }), ) @@ -602,7 +614,31 @@ func (pg *TransactionsPage) HandleUserInteractions() { } for pg.exportBtn.Clicked() { - // TODO: implement logic when export clicked + exportModal := modal.NewCustomModal(pg.Load). + Title(values.String(values.StrExportTransaction)). + Body(values.String(values.StrExportTransactionsMsg)). + SetNegativeButtonText(values.String(values.StrCancel)). + SetPositiveButtonText(values.String(values.StrExport)). + SetPositiveButtonCallback(func(_ bool, im *modal.InfoModal) bool { + assets := []sharedW.Asset{pg.selectedWallet} + if pg.selectedWallet == nil { + assets = pg.assetWallets + } + go func() { + fileName := filepath.Join(pg.AssetsManager.RootDir(), "exports", fmt.Sprintf("transaction_export_%d.csv", time.Now().Unix())) + err := exportTxs(assets, fileName) + if err != nil { + errModal := modal.NewErrorModal(pg.Load, fmt.Errorf("Error exporting your wallet(s) transactions: %v", err).Error(), modal.DefaultClickFunc()) + pg.ParentWindow().ShowModal(errModal) + return + } + + infoModal := modal.NewSuccessModal(pg.Load, values.StringF(values.StrExportTransactionSuccessMsg, fileName), modal.DefaultClickFunc()) + pg.ParentWindow().ShowModal(infoModal) + }() + return true + }) + pg.ParentWindow().ShowModal(exportModal) } if pg.orderDropDown.Changed() { @@ -619,6 +655,64 @@ func (pg *TransactionsPage) HandleUserInteractions() { } } +func exportTxs(assets []sharedW.Asset, fileName string) error { + if err := os.MkdirAll(filepath.Dir(fileName), utils.UserFilePerm); err != nil { + return fmt.Errorf("os.MkdirAll error: %w", err) + } + + var success bool + defer func() { + if !success { + os.Remove(fileName) + } + }() + + f, err := os.Create(fileName) + if err != nil { + return fmt.Errorf("os.Create error: %w", err) + } + defer f.Close() + + headers := []string{values.String(values.StrTime), values.String(values.StrHash), values.String(values.StrType), values.String(values.StrDirection), values.String(values.StrFee), values.String(values.StrAmount)} + + writer := csv.NewWriter(f) + writer.UseCRLF = runtime.GOOS == "windows" + err = writer.Write(headers) + if err != nil { + return fmt.Errorf("csv.Writer.Write error: %w", err) + } + + for _, a := range assets { + txs, err := a.GetTransactionsRaw(0, math.MaxInt32, utils.TxFilterAll, true, "") + if err != nil { + return fmt.Errorf("wallet.GetTransactionsRaw error: %w", err) + } + + // Write txs to file. + for _, tx := range txs { + err := writer.Write([]string{ + time.Unix(tx.Timestamp, 0).String(), + tx.Hash, + tx.Type, + txhelper.TxDirectionString(tx.Direction), + a.ToAmount(tx.Fee).String(), + a.ToAmount(tx.Amount).String(), + }) + if err != nil { + return fmt.Errorf("csv.Writer.Write error: %v", err) + } + + writer.Flush() + if err = writer.Error(); err != nil { + return fmt.Errorf("csv.Writer error: %w", err) + } + } + } + + success = true + return nil +} + func (pg *TransactionsPage) listenForTxNotifications() { txAndBlockNotificationListener := &sharedW.TxAndBlockNotificationListener{ OnTransaction: func(walletID int, transaction *sharedW.Transaction) { diff --git a/ui/values/const.go b/ui/values/const.go index 100a4adff..13904fd2c 100644 --- a/ui/values/const.go +++ b/ui/values/const.go @@ -11,4 +11,5 @@ const ( DEXCurrencyPairGroup DEXOrderTypes StartPageDropdownGroup + TxExportOptionsGroup ) diff --git a/ui/values/localizable/en.go b/ui/values/localizable/en.go index 52aeb1c2b..8338de81a 100644 --- a/ui/values/localizable/en.go +++ b/ui/values/localizable/en.go @@ -902,4 +902,10 @@ const EN = ` "matrix" = "Matrix" "createAssetWalletToVoteMsg" = "You need to create a %s wallet to vote." "getTicketsNow" = "Get Tickets Now" +"exportTransaction" = "Export Transaction" +"exportTransactionsMsg" = "Export the transactions recorded in your wallet(s). Your transactions will be processed in the background and you'll be notified when it's ready." +"exportType" = "Export Type" +"time" = "Time" +"direction" = "Direction" +"exportTransactionSuccessMsg" = "Your transactions have been exported successfully and saved to %s." ` diff --git a/ui/values/strings.go b/ui/values/strings.go index f820f0530..c5ae6ab39 100644 --- a/ui/values/strings.go +++ b/ui/values/strings.go @@ -1011,4 +1011,10 @@ const ( StrWebsite = "website" StrCreateAssetWalletToVoteMsg = "createAssetWalletToVoteMsg" StrGetTicketsNow = "getTicketsNow" + StrExportTransaction = "exportTransaction" + StrExportTransactionsMsg = "exportTransactionsMsg" + StrExportType = "exportType" + StrTime = "time" + StrDirection = "direction" + StrExportTransactionSuccessMsg = "exportTransactionSuccessMsg" )