feat(@desktop/wallet): Implement Transaction details as per new design

fixes #7214
This commit is contained in:
Khushboo Mehta 2022-09-05 11:15:47 +02:00 committed by Khushboo-dev-cpp
parent cdfbb4ac87
commit 928e1999d9
23 changed files with 759 additions and 181 deletions

View File

@ -61,6 +61,9 @@ QtObject:
proc hex2Eth*(self: Utils, value: string): string {.slot.} = proc hex2Eth*(self: Utils, value: string): string {.slot.} =
return stripTrailingZeroes(conversion.wei2Eth(stint.fromHex(StUint[256], value))) return stripTrailingZeroes(conversion.wei2Eth(stint.fromHex(StUint[256], value)))
proc hex2Gwei*(self: Utils, value: string): string {.slot.} =
return stripTrailingZeroes(conversion.wei2Eth(stint.fromHex(StUint[256], value)*1000000000))
proc gwei2Hex*(self: Utils, gwei: float): string {.slot.} = proc gwei2Hex*(self: Utils, gwei: float): string {.slot.} =
return "0x" & conversion.gwei2Wei(gwei).toHex() return "0x" & conversion.gwei2Wei(gwei).toHex()

View File

@ -113,3 +113,6 @@ proc getChainIdForBrowser*(self: Controller): int =
proc getEstimatedTime*(self: Controller, chainId: int, maxFeePerGas: string): EstimatedTime = proc getEstimatedTime*(self: Controller, chainId: int, maxFeePerGas: string): EstimatedTime =
return self.transactionService.getEstimatedTime(chainId, maxFeePerGas) return self.transactionService.getEstimatedTime(chainId, maxFeePerGas)
proc getLastTxBlockNumber*(self: Controller): string =
return self.transactionService.getLastTxBlockNumber(self.networkService.getNetworkForBrowser().chainId)

View File

@ -72,3 +72,6 @@ method getEstimatedTime*(self: AccessInterface, chainId: int, maxFeePerGas: stri
# inheritance, which is not well supported in Nim. # inheritance, which is not well supported in Nim.
method viewDidLoad*(self: AccessInterface) {.base.} = method viewDidLoad*(self: AccessInterface) {.base.} =
raise newException(ValueError, "No implementation available") raise newException(ValueError, "No implementation available")
method getLastTxBlockNumber*(self: AccessInterface): string {.base.} =
raise newException(ValueError, "No implementation available")

View File

@ -24,6 +24,9 @@ type
txHash: string txHash: string
multiTransactionID: int multiTransactionID: int
isTimeStamp: bool isTimeStamp: bool
baseGasFees: string
totalFees: string
maxTotalFees: string
proc initItem*( proc initItem*(
id: string, id: string,
@ -47,7 +50,10 @@ proc initItem*(
input: string, input: string,
txHash: string, txHash: string,
multiTransactionID: int, multiTransactionID: int,
isTimeStamp: bool isTimeStamp: bool,
baseGasFees: string,
totalFees: string,
maxTotalFees: string
): Item = ): Item =
result.id = id result.id = id
result.typ = typ result.typ = typ
@ -71,6 +77,9 @@ proc initItem*(
result.txHash = txHash result.txHash = txHash
result.multiTransactionID = multiTransactionID result.multiTransactionID = multiTransactionID
result.isTimeStamp = isTimeStamp result.isTimeStamp = isTimeStamp
result.baseGasFees = baseGasFees
result.totalFees = totalFees
result.maxTotalFees = maxTotalFees
proc `$`*(self: Item): string = proc `$`*(self: Item): string =
result = fmt"""AllTokensItem( result = fmt"""AllTokensItem(
@ -96,6 +105,9 @@ proc `$`*(self: Item): string =
txHash: {self.txHash}, txHash: {self.txHash},
multiTransactionID: {self.multiTransactionID}, multiTransactionID: {self.multiTransactionID},
isTimeStamp: {self.isTimeStamp}, isTimeStamp: {self.isTimeStamp},
baseGasFees: {self.baseGasFees},
totalFees: {self.totalFees},
maxTotalFees: {self.maxTotalFees},
]""" ]"""
proc getId*(self: Item): string = proc getId*(self: Item): string =
@ -163,3 +175,12 @@ proc getMultiTransactionID*(self: Item): int =
proc getIsTimeStamp*(self: Item): bool = proc getIsTimeStamp*(self: Item): bool =
return self.isTimeStamp return self.isTimeStamp
proc getBaseGasFees*(self: Item): string =
return self.baseGasFees
proc getTotalFees*(self: Item): string =
return self.totalFees
proc getMaxTotalFees*(self: Item): string =
return self.maxTotalFees

View File

@ -1,4 +1,4 @@
import NimQml, Tables, strutils, strformat, sequtils, tables, sugar, algorithm, std/[times, os], stint import NimQml, Tables, strutils, strformat, sequtils, tables, sugar, algorithm, std/[times, os], stint, parseutils
import ./item import ./item
import ../../../../../app_service/service/eth/utils as eth_service_utils import ../../../../../app_service/service/eth/utils as eth_service_utils
@ -28,6 +28,9 @@ type
TxHash TxHash
MultiTransactionID MultiTransactionID
IsTimeStamp IsTimeStamp
BaseGasFees
TotalFees
MaxTotalFees
QtObject: QtObject:
type type
@ -88,7 +91,10 @@ QtObject:
ModelRole.Input.int:"input", ModelRole.Input.int:"input",
ModelRole.TxHash.int:"txHash", ModelRole.TxHash.int:"txHash",
ModelRole.MultiTransactionID.int:"multiTransactionID", ModelRole.MultiTransactionID.int:"multiTransactionID",
ModelRole.IsTimeStamp.int: "isTimeStamp" ModelRole.IsTimeStamp.int: "isTimeStamp",
ModelRole.BaseGasFees.int: "baseGasFees",
ModelRole.TotalFees.int: "totalFees",
ModelRole.MaxTotalFees.int: "maxTotalFees"
}.toTable }.toTable
method data(self: Model, index: QModelIndex, role: int): QVariant = method data(self: Model, index: QModelIndex, role: int): QVariant =
@ -146,6 +152,12 @@ QtObject:
result = newQVariant(item.getMultiTransactionID()) result = newQVariant(item.getMultiTransactionID())
of ModelRole.IsTimeStamp: of ModelRole.IsTimeStamp:
result = newQVariant(item.getIsTimeStamp()) result = newQVariant(item.getIsTimeStamp())
of ModelRole.BaseGasFees:
result = newQVariant(item.getBaseGasFees())
of ModelRole.TotalFees:
result = newQVariant(item.getTotalFees())
of ModelRole.MaxTotalFees:
result = newQVariant(item.getMaxTotalFees())
proc setItems*(self: Model, items: seq[Item]) = proc setItems*(self: Model, items: seq[Item]) =
self.beginResetModel() self.beginResetModel()
@ -206,7 +218,10 @@ QtObject:
t.input, t.input,
t.txHash, t.txHash,
t.multiTransactionID, t.multiTransactionID,
false false,
t.baseGasFees,
t.totalFees,
t.maxTotalFees,
)) ))
var allTxs = self.items.concat(newTxItems) var allTxs = self.items.concat(newTxItems)
@ -219,7 +234,7 @@ QtObject:
for tx in allTxs: for tx in allTxs:
let duration = fromUnix(tx.getTimestamp()) - tempTimeStamp let duration = fromUnix(tx.getTimestamp()) - tempTimeStamp
if(duration.inDays != 0): if(duration.inDays != 0):
itemsWithDateHeaders.add(initItem("", "", "", "", "", tx.getTimestamp(), "", "", "", "", "", "", "", "", "", 0, "", "", "", "", 0, true)) itemsWithDateHeaders.add(initItem("", "", "", "", "", tx.getTimestamp(), "", "", "", "", "", "", "", "", "", 0, "", "", "", "", 0, true, "", "", ""))
itemsWithDateHeaders.add(tx) itemsWithDateHeaders.add(tx)
tempTimeStamp = fromUnix(tx.getTimestamp()) tempTimeStamp = fromUnix(tx.getTimestamp())

View File

@ -113,3 +113,6 @@ method getChainIdForBrowser*(self: Module): int =
method getEstimatedTime*(self: Module, chainId: int, maxFeePerGas: string): int = method getEstimatedTime*(self: Module, chainId: int, maxFeePerGas: string): int =
return self.controller.getEstimatedTime(chainId, maxFeePerGas).int return self.controller.getEstimatedTime(chainId, maxFeePerGas).int
method getLastTxBlockNumber*(self: Module): string =
return self.controller.getLastTxBlockNumber()

View File

@ -150,3 +150,6 @@ QtObject:
proc getEstimatedTime*(self: View, chainId: int, maxFeePerGas: string): int {.slot.} = proc getEstimatedTime*(self: View, chainId: int, maxFeePerGas: string): int {.slot.} =
return self.delegate.getEstimatedTime(chainId, maxFeePerGas) return self.delegate.getEstimatedTime(chainId, maxFeePerGas)
proc getLastTxBlockNumber*(self: View): string {.slot.} =
return self.delegate.getLastTxBlockNumber()

View File

@ -49,6 +49,21 @@ type
input*: string input*: string
txHash*: string txHash*: string
multiTransactionID*: int multiTransactionID*: int
baseGasFees*: string
totalFees*: string
maxTotalFees*: string
proc getTotalFees(tip: string, baseFee: string, gasUsed: string, maxFee: string): string =
var maxFees = stint.fromHex(Uint256, maxFee)
var totalGasUsed = stint.fromHex(Uint256, tip) + stint.fromHex(Uint256, baseFee)
if totalGasUsed > maxFees:
totalGasUsed = maxFees
var totalGasUsedInHex = (totalGasUsed * stint.fromHex(Uint256, gasUsed)).toHex
return totalGasUsedInHex
proc getMaxTotalFees(maxFee: string, gasLimit: string): string =
return (stint.fromHex(Uint256, maxFee) * stint.fromHex(Uint256, gasLimit)).toHex
proc toTransactionDto*(jsonObj: JsonNode): TransactionDto = proc toTransactionDto*(jsonObj: JsonNode): TransactionDto =
result = TransactionDto() result = TransactionDto()
@ -73,6 +88,9 @@ proc toTransactionDto*(jsonObj: JsonNode): TransactionDto =
discard jsonObj.getProp("input", result.input) discard jsonObj.getProp("input", result.input)
discard jsonObj.getProp("txHash", result.txHash) discard jsonObj.getProp("txHash", result.txHash)
discard jsonObj.getProp("multiTransactionID", result.multiTransactionID) discard jsonObj.getProp("multiTransactionID", result.multiTransactionID)
discard jsonObj.getProp("base_gas_fee", result.baseGasFees)
result.totalFees = getTotalFees(result.maxPriorityFeePerGas, result.baseGasFees, result.gasUsed, result.maxFeePerGas)
result.maxTotalFees = getMaxTotalFees(result.maxFeePerGas, result.gasLimit)
proc cmpTransactions*(x, y: TransactionDto): int = proc cmpTransactions*(x, y: TransactionDto): int =
# Sort proc to compare transactions from a single account. # Sort proc to compare transactions from a single account.

View File

@ -422,3 +422,11 @@ QtObject:
except Exception as e: except Exception as e:
error "Error estimating transaction time", message = e.msg error "Error estimating transaction time", message = e.msg
return EstimatedTime.Unknown return EstimatedTime.Unknown
proc getLastTxBlockNumber*(self: Service, chainId: int): string =
try:
let response = eth.getBlockByNumber(chainId, "latest")
return response.result{"number"}.getStr
except Exception as e:
error "Error getting latest block number", message = e.msg
return ""

@ -1 +1 @@
Subproject commit 7108ac87cffe529d011ec1c14473630e52980282 Subproject commit c3bbb396ecd5ad25df565d64429aaba869df4b49

View File

@ -54,6 +54,7 @@ Item {
id: walletContainer id: walletContainer
RightTabView { RightTabView {
store: root.store store: root.store
contactsStore: root.contactsStore
sendModal: root.sendModal sendModal: root.sendModal
} }
} }

View File

@ -6,6 +6,7 @@ import StatusQ.Controls 0.1
import utils 1.0 import utils 1.0
import shared.views 1.0 import shared.views 1.0
import "./"
import "../stores" import "../stores"
import "../panels" import "../panels"
import "../views/collectibles" import "../views/collectibles"
@ -15,6 +16,7 @@ Item {
property alias currentTabIndex: walletTabBar.currentIndex property alias currentTabIndex: walletTabBar.currentIndex
property var store property var store
property var contactsStore
property var sendModal property var sendModal
ColumnLayout { ColumnLayout {
@ -80,6 +82,10 @@ Item {
} }
HistoryView { HistoryView {
account: RootStore.currentAccount account: RootStore.currentAccount
onLaunchTransactionDetail: {
transactionDetailView.transaction = transaction
stack.currentIndex = 3
}
} }
} }
} }
@ -94,6 +100,14 @@ Item {
Layout.fillHeight: true Layout.fillHeight: true
onGoBack: stack.currentIndex = 0 onGoBack: stack.currentIndex = 0
} }
TransactionDetailView {
id: transactionDetailView
Layout.fillWidth: true
Layout.fillHeight: true
sendModal: root.sendModal
contactsStore: root.contactsStore
onGoBack: stack.currentIndex = 0
}
} }
WalletFooter { WalletFooter {

View File

@ -26,6 +26,16 @@ Item {
id: _internal id: _internal
property bool loading: false property bool loading: false
property string error: "" property string error: ""
function saveAddress(name, address) {
loading = true
error = RootStore.createOrUpdateSavedAddress(name, address)
loading = false
}
function deleteSavedAddress(address) {
loading = true
error = RootStore.deleteSavedAddress(address)
loading = false
}
} }
Item { Item {
@ -65,155 +75,6 @@ Item {
} }
} }
Component {
id: delegateSavedAddress
StatusListItem {
id: savedAddress
title: name
objectName: name
subTitle: name + " \u2022 " + Utils.getElidedCompressedPk(address)
implicitWidth: parent.width
color: "transparent"
border.color: Theme.palette.baseColor5
//TODO uncomment when #6456 is fixed
//titleTextIcon: RootStore.favouriteAddress ? "star-icon" : ""
statusListItemComponentsSlot.spacing: 0
property bool showButtons: sensor.containsMouse
components: [
StatusRoundButton {
icon.color: savedAddress.showButtons ? Theme.palette.directColor1 : Theme.palette.baseColor1
type: StatusRoundButton.Type.Tertiary
icon.name: "send"
onClicked: {
root.sendModal.open(address);
}
},
CopyToClipBoardButton {
type: StatusRoundButton.Type.Tertiary
icon.color: savedAddress.showButtons ? Theme.palette.directColor1 : Theme.palette.baseColor1
store: RootStore
textToCopy: address
},
//TODO uncomment when #6456 is fixed
// StatusRoundButton {
// icon.color: savedAddress.showButtons ? Theme.palette.directColor1 : Theme.palette.baseColor1
// type: StatusRoundButton.Type.Tertiary
// icon.name: savedAddress.favouriteAddress ? "favourite" : "unfavourite"
// onClicked: {
// RootStore.setFavourite();
// }
// },
StatusRoundButton {
icon.color: savedAddress.showButtons ? Theme.palette.directColor1 : Theme.palette.baseColor1
type: StatusRoundButton.Type.Tertiary
icon.name: "more"
onClicked: {
editDeleteMenu.openMenu(name, address);
}
}
]
}
}
StatusPopupMenu {
id: editDeleteMenu
property string contactName
property string contactAddress
function openMenu(name, address) {
contactName = name;
contactAddress = address;
popup();
}
onClosed: {
contactName = "";
contactAddress = "";
}
StatusMenuItem {
text: qsTr("Edit")
objectName: "editSavedAddress"
icon.name: "pencil-outline"
onTriggered: {
Global.openPopup(addEditSavedAddress,
{
edit: true,
address: editDeleteMenu.contactAddress,
name: editDeleteMenu.contactName
})
}
}
StatusMenuSeparator { }
StatusMenuItem {
text: qsTr("Delete")
type: StatusMenuItem.Type.Danger
icon.name: "delete"
objectName: "deleteSavedAddress"
onTriggered: {
deleteAddressConfirm.name = editDeleteMenu.contactName;
deleteAddressConfirm.address = editDeleteMenu.contactAddress;
deleteAddressConfirm.open()
}
}
}
Component {
id: addEditSavedAddress
AddEditSavedAddressPopup {
id: addEditModal
anchors.centerIn: parent
onClosed: destroy()
contactsStore: root.contactsStore
onSave: {
_internal.loading = true
_internal.error = RootStore.createOrUpdateSavedAddress(name, address)
_internal.loading = false
close()
}
}
}
StatusModal {
id: deleteAddressConfirm
property string address
property string name
// NOTE: the `text` property was created as a workaround because
// setting StatusBaseText.text to `qsTr("...").arg("...")`
// caused no text to render
property string text: qsTr("Are you sure you want to remove '%1' from your saved addresses?").arg(name)
anchors.centerIn: parent
header.title: qsTr("Are you sure?")
header.subTitle: name
contentItem: StatusBaseText {
anchors.centerIn: parent
height: contentHeight + topPadding + bottomPadding
text: deleteAddressConfirm.text
font.pixelSize: 15
color: Theme.palette.directColor1
wrapMode: Text.Wrap
topPadding: Style.current.padding
rightPadding: Style.current.padding
bottomPadding: Style.current.padding
leftPadding: Style.current.padding
}
rightButtons: [
StatusButton {
text: qsTr("Cancel")
onClicked: deleteAddressConfirm.close()
},
StatusButton {
type: StatusBaseButton.Type.Danger
objectName: "confirmDeleteSavedAddress"
text: qsTr("Delete")
onClicked: {
_internal.loading = true
_internal.error = RootStore.deleteSavedAddress(deleteAddressConfirm.address)
deleteAddressConfirm.close()
_internal.loading = false
}
}
]
}
SavedAddressesError { SavedAddressesError {
id: errorMessage id: errorMessage
anchors.top: header.bottom anchors.top: header.bottom
@ -242,6 +103,32 @@ Item {
visible: listView.count > 0 visible: listView.count > 0
spacing: 5 spacing: 5
model: RootStore.savedAddresses model: RootStore.savedAddresses
delegate: delegateSavedAddress delegate: SavedAddressesDelegate {
name: model.name
address: model.address
store: RootStore
contactsStore: root.contactsStore
onOpenSendModal: root.sendModal.open(address);
saveAddress: function(name, address) {
_internal.saveAddress(name, address)
}
deleteSavedAddress: function(address) {
_internal.deleteSavedAddress(address)
}
}
}
Component {
id: addEditSavedAddress
AddEditSavedAddressPopup {
id: addEditModal
anchors.centerIn: parent
onClosed: destroy()
contactsStore: root.contactsStore
onSave: {
_internal.saveAddress(name, address)
close()
}
}
} }
} }

View File

@ -0,0 +1,176 @@
import QtQuick 2.13
import QtQuick.Controls 2.13
import utils 1.0
import StatusQ.Controls 0.1
import StatusQ.Components 0.1
import StatusQ.Core 0.1
import StatusQ.Core.Theme 0.1
import StatusQ.Popups 0.1
import shared.controls 1.0
import "../popups"
import "../controls"
StatusListItem {
id: root
property var store
property var contactsStore
property string name
property string address
property var saveAddress: function (name, address) {}
property var deleteSavedAddress: function (address) {}
signal openSendModal()
implicitWidth: parent.width
title: name
objectName: name
subTitle: name + " \u2022 " + Utils.getElidedCompressedPk(address)
color: "transparent"
border.color: Theme.palette.baseColor5
//TODO uncomment when #6456 is fixed
//titleTextIcon: RootStore.favouriteAddress ? "star-icon" : ""
statusListItemComponentsSlot.spacing: 0
property bool showButtons: sensor.containsMouse
components: [
StatusRoundButton {
icon.color: root.showButtons ? Theme.palette.directColor1 : Theme.palette.baseColor1
type: StatusRoundButton.Type.Tertiary
icon.name: "send"
onClicked: openSendModal()
},
CopyToClipBoardButton {
id: copyButton
type: StatusRoundButton.Type.Tertiary
icon.color: root.showButtons ? Theme.palette.directColor1 : Theme.palette.baseColor1
store: root.store
textToCopy: root.address
},
//TODO uncomment when #6456 is fixed
// StatusRoundButton {
// icon.color: root.showButtons ? Theme.palette.directColor1 : Theme.palette.baseColor1
// type: StatusRoundButton.Type.Tertiary
// icon.name: root.favouriteAddress ? "favourite" : "unfavourite"
// onClicked: {
// RootStore.setFavourite();
// }
// },
StatusRoundButton {
visible: !!root.name
icon.color: root.showButtons ? Theme.palette.directColor1 : Theme.palette.baseColor1
type: StatusRoundButton.Type.Tertiary
icon.name: "more"
onClicked: {
editDeleteMenu.openMenu(root.name, root.address);
}
},
StatusRoundButton {
visible: !root.name
icon.color: root.showButtons ? Theme.palette.directColor1 : Theme.palette.baseColor1
type: StatusRoundButton.Type.Tertiary
icon.name: "add"
onClicked: {
Global.openPopup(addEditSavedAddress,
{
addAddress: true,
address: root.address
})
}
}
]
StatusPopupMenu {
id: editDeleteMenu
property string contactName
property string contactAddress
function openMenu(name, address) {
contactName = name;
contactAddress = address;
popup();
}
onClosed: {
contactName = "";
contactAddress = "";
}
StatusMenuItem {
text: qsTr("Edit")
objectName: "editroot"
assetSettings.name: "pencil-outline"
onTriggered: {
Global.openPopup(addEditSavedAddress,
{
edit: true,
address: editDeleteMenu.contactAddress,
name: editDeleteMenu.contactName
})
}
}
StatusMenuSeparator { }
StatusMenuItem {
text: qsTr("Delete")
type: StatusMenuItem.Type.Danger
assetSettings.name: "delete"
objectName: "deleteSavedAddress"
onTriggered: {
deleteAddressConfirm.name = editDeleteMenu.contactName;
deleteAddressConfirm.address = editDeleteMenu.contactAddress;
deleteAddressConfirm.open()
}
}
}
Component {
id: addEditSavedAddress
AddEditSavedAddressPopup {
id: addEditModal
anchors.centerIn: parent
onClosed: destroy()
contactsStore: root.contactsStore
onSave: {
root.saveAddress(name, address)
close()
}
}
}
StatusModal {
id: deleteAddressConfirm
property string address
property string name
anchors.centerIn: parent
header.title: qsTr("Are you sure?")
header.subTitle: name
contentItem: StatusBaseText {
anchors.centerIn: parent
height: contentHeight + topPadding + bottomPadding
text: qsTr("Are you sure you want to remove '%1' from your saved addresses?").arg(name)
font.pixelSize: 15
color: Theme.palette.directColor1
wrapMode: Text.Wrap
topPadding: Style.current.padding
rightPadding: Style.current.padding
bottomPadding: Style.current.padding
leftPadding: Style.current.padding
}
rightButtons: [
StatusButton {
text: qsTr("Cancel")
onClicked: deleteAddressConfirm.close()
},
StatusButton {
type: StatusBaseButton.Type.Danger
objectName: "confirmDeleteSavedAddress"
text: qsTr("Delete")
onClicked: {
root.deleteSavedAddress(deleteAddressConfirm.address)
deleteAddressConfirm.close()
}
}
]
}
}

View File

@ -25,12 +25,13 @@ StatusListItem {
property string resolvedSymbol: root.symbol != "" ? root.symbol : "ETH" property string resolvedSymbol: root.symbol != "" ? root.symbol : "ETH"
property string savedAddressName property string savedAddressName
state: "normal"
asset.isImage: true asset.isImage: true
asset.name: Style.png("tokens/%1".arg(resolvedSymbol)) asset.name: Style.png("tokens/%1".arg(resolvedSymbol))
statusListItemTitle.font.weight: Font.Medium title: modelData !== undefined && !!modelData ?
title: isIncoming ? qsTr("Receive %1").arg(resolvedSymbol) : !!savedAddressName ? isIncoming ? qsTr("Receive %1").arg(resolvedSymbol) : !!savedAddressName ?
qsTr("Send %1 to %2").arg(resolvedSymbol).arg(savedAddressName) : qsTr("Send %1 to %2").arg(resolvedSymbol).arg(savedAddressName) :
qsTr("Send %1 to %2").arg(resolvedSymbol).arg(Utils.compactAddress(modelData.to, 4)) qsTr("Send %1 to %2").arg(resolvedSymbol).arg(Utils.compactAddress(modelData.to, 4)): ""
subTitle: shortTimeStamp subTitle: shortTimeStamp
inlineTagModel: 1 inlineTagModel: 1
inlineTagDelegate: InformationTag { inlineTagDelegate: InformationTag {
@ -60,8 +61,8 @@ StatusListItem {
height: 18 height: 18
} }
StatusBaseText { StatusBaseText {
id: cryptoValueText
text: "%1 %2".arg(cryptoValue).arg(resolvedSymbol) text: "%1 %2".arg(cryptoValue).arg(resolvedSymbol)
font.pixelSize: 15
color: Theme.palette.directColor1 color: Theme.palette.directColor1
} }
} }
@ -92,4 +93,41 @@ StatusListItem {
height: 10 height: 10
} }
} }
states: [
State {
name: "normal"
PropertyChanges {
target: asset
width: 40
height: 40
}
PropertyChanges {
target: statusListItemTitle
font.weight: Font.Medium
font.pixelSize: 15
}
PropertyChanges {
target: cryptoValueText
font.pixelSize: 15
}
},
State {
name: "big"
PropertyChanges {
target: asset
width: 50
height: 50
}
PropertyChanges {
target: statusListItemTitle
font.weight: Font.Bold
font.pixelSize: 17
}
PropertyChanges {
target: cryptoValueText
font.pixelSize: 17
}
}
]
} }

View File

@ -27,3 +27,5 @@ InformationTile 1.0 InformationTile.qml
SocialLinkPreview 1.0 SocialLinkPreview.qml SocialLinkPreview 1.0 SocialLinkPreview.qml
AssetsDetailsHeader 1.0 AssetsDetailsHeader.qml AssetsDetailsHeader 1.0 AssetsDetailsHeader.qml
InformationTag 1.0 InformationTag.qml InformationTag 1.0 InformationTag.qml
TransactionDetailsHeader.qml 1.0 TransactionDetailsHeader.qml
SavedAddressesDelegate 1.0 SavedAddressesDelegate.qml

View File

@ -0,0 +1,115 @@
import QtQuick 2.13
import QtQuick.Controls 2.13
import QtQml.Models 2.14
import utils 1.0
import shared.controls 1.0
import shared.panels 1.0
import StatusQ.Core 0.1
import StatusQ.Core.Theme 0.1
import StatusQ.Controls 0.1
import StatusQ.Controls.Validators 0.1
import StatusQ.Popups.Dialog 0.1
import "../stores"
StatusDialog {
id: root
property bool edit: false
property bool addAddress: false
property string address
property alias name: nameInput.text
property var contactsStore
signal save(string name, string address)
QtObject {
id: d
property int validationMode: root.edit ?
StatusInput.ValidationMode.Always
: StatusInput.ValidationMode.OnlyWhenDirty
property bool valid: addressInput.isValid && nameInput.valid // TODO: Add network preference and emoji
property bool dirty: nameInput.input.dirty
}
width: 574
height: 490
header: StatusDialogHeader {
headline.title: edit ? qsTr("Edit saved address") : qsTr("Add saved address")
headline.subtitle: edit ? name : ""
}
onOpened: {
if(edit || addAddress) {
addressInput.input.text = root.address
}
nameInput.input.edit.forceActiveFocus(Qt.MouseFocusReason)
}
Column {
width: parent.width
height: childrenRect.height
topPadding: Style.current.xlPadding
spacing: Style.current.bigPadding
StatusInput {
id: nameInput
implicitWidth: parent.width
input.edit.objectName: "savedAddressNameInput"
minimumHeight: 56
maximumHeight: 56
placeholderText: qsTr("Enter a name")
label: qsTr("Name")
validators: [
StatusMinLengthValidator {
minLength: 1
errorMessage: qsTr("Name must not be blank")
},
StatusRegularExpressionValidator {
regularExpression: /^[^<>]+$/
errorMessage: qsTr("This is not a valid account name")
}
]
charLimit: 40
validationMode: d.validationMode
}
// To-Do use StatusInput within the below component
RecipientSelector {
id: addressInput
implicitWidth: parent.width
inputWidth: implicitWidth
accounts: RootStore.accounts
contactsStore: root.contactsStore
label: qsTr("Address")
input.textField.objectName: "savedAddressAddressInput"
input.placeholderText: qsTr("Enter ENS Name or Ethereum Address")
labelFont.pixelSize: 15
labelFont.weight: Font.Normal
input.implicitHeight: 56
input.textField.anchors.rightMargin: 0
isSelectorVisible: false
addContactEnabled: false
onSelectedRecipientChanged: {
root.address = selectedRecipient.address
}
readOnly: root.edit || root.addAddress
wrongInputValidationError: qsTr("Please enter a valid ENS name OR Ethereum Address")
}
}
footer: StatusDialogFooter {
rightButtons: ObjectModel {
StatusButton {
text: root.edit ? qsTr("Save") : qsTr("Add address")
enabled: d.valid && d.dirty
onClicked: root.save(name, address)
objectName: "addSavedAddress"
}
}
}
}

View File

@ -21,3 +21,4 @@ ProfilePopup 1.0 ProfilePopup.qml
ImageCropWorkflow 1.0 ImageCropWorkflow.qml ImageCropWorkflow 1.0 ImageCropWorkflow.qml
ImportCommunityPopup 1.0 ImportCommunityPopup.qml ImportCommunityPopup 1.0 ImportCommunityPopup.qml
DisplayNamePopup 1.0 DisplayNamePopup.qml DisplayNamePopup 1.0 DisplayNamePopup.qml
AddEditSavedAddressPopup 1.0 AddEditSavedAddressPopup.qml

View File

@ -38,6 +38,7 @@ QtObject {
property var historyTransactions: walletSectionTransactions.model property var historyTransactions: walletSectionTransactions.model
property bool isNonArchivalNode: history.isNonArchivalNode property bool isNonArchivalNode: history.isNonArchivalNode
property var currentAccount: walletSectionCurrent
property var walletTokensModule: walletSectionAllTokens property var walletTokensModule: walletSectionAllTokens
property var tokens: walletSectionAllTokens.all property var tokens: walletSectionAllTokens.all
property var accounts: walletSectionAccounts.model property var accounts: walletSectionAccounts.model
@ -170,6 +171,10 @@ QtObject {
return globalUtils.hex2Eth(value) return globalUtils.hex2Eth(value)
} }
function hex2Gwei(value) {
return globalUtils.hex2Gwei(value)
}
function findTokenSymbolByAddress(address) { function findTokenSymbolByAddress(address) {
return walletSectionAllTokens.findTokenSymbolByAddress(address) return walletSectionAllTokens.findTokenSymbolByAddress(address)
@ -178,4 +183,20 @@ QtObject {
function getNameForSavedWalletAddress(address) { function getNameForSavedWalletAddress(address) {
return walletSectionSavedAddresses.getNameByAddress(address) return walletSectionSavedAddresses.getNameByAddress(address)
} }
function createOrUpdateSavedAddress(name, address) {
return walletSectionSavedAddresses.createOrUpdateSavedAddress(name, address)
}
function deleteSavedAddress(address) {
return walletSectionSavedAddresses.deleteSavedAddress(address)
}
function getLatestBlockNumber() {
return walletSectionTransactions.getLastTxBlockNumber()
}
function getGasEthValue(gweiValue, gasLimit) {
return profileSectionModule.ensUsernamesModule.getGasEthValue(gweiValue, gasLimit)
}
} }

View File

@ -21,6 +21,8 @@ ColumnLayout {
property int pageSize: 20 // number of transactions per page property int pageSize: 20 // number of transactions per page
property bool isLoading: false property bool isLoading: false
signal launchTransactionDetail(var transaction)
function fetchHistory() { function fetchHistory() {
if (RootStore.isFetchingHistory(historyView.account.address)) { if (RootStore.isFetchingHistory(historyView.account.address)) {
isLoading = true isLoading = true
@ -99,7 +101,7 @@ ColumnLayout {
StatusListItem { StatusListItem {
property var modelData property var modelData
height: 40 height: 40
title: Utils.formatShortDate(modelData.timestamp * 1000, RootStore.accountSensitiveSettings.is24hTimeFormat) title: modelData !== undefined && !!modelData ? Utils.formatShortDate(modelData.timestamp * 1000, RootStore.accountSensitiveSettings.is24hTimeFormat) : ""
statusListItemTitle.color: Theme.palette.baseColor1 statusListItemTitle.color: Theme.palette.baseColor1
color: Theme.palette.statusListItem.backgroundColor color: Theme.palette.statusListItem.backgroundColor
sensor.enabled: false sensor.enabled: false
@ -109,21 +111,18 @@ ColumnLayout {
Component { Component {
id: transactionDelegate id: transactionDelegate
TransactionDelegate { TransactionDelegate {
isIncoming: modelData !== undefined ? modelData.to === account.address: false isIncoming: modelData !== undefined && !!modelData ? modelData.to === account.address: false
currentCurrency: RootStore.currentCurrency currentCurrency: RootStore.currentCurrency
cryptoValue: modelData !== undefined ? RootStore.hex2Eth(modelData.value) : "" cryptoValue: modelData !== undefined && !!modelData ? RootStore.hex2Eth(modelData.value) : ""
fiatValue: RootStore.getFiatValue(cryptoValue, resolvedSymbol, RootStore.currentCurrency) fiatValue: RootStore.getFiatValue(cryptoValue, resolvedSymbol, RootStore.currentCurrency)
networkIcon: modelData !== undefined ? RootStore.getNetworkIcon(modelData.chainId) : "" networkIcon: modelData !== undefined && !!modelData ? RootStore.getNetworkIcon(modelData.chainId) : ""
networkColor: modelData !== undefined ? RootStore.getNetworkColor(modelData.chainId) : "" networkColor: modelData !== undefined && !!modelData ? RootStore.getNetworkColor(modelData.chainId) : ""
networkName: modelData !== undefined ? RootStore.getNetworkShortName(modelData.chainId) : "" networkName: modelData !== undefined && !!modelData ? RootStore.getNetworkShortName(modelData.chainId) : ""
symbol: modelData !== undefined ? RootStore.findTokenSymbolByAddress(modelData.contract) : "" symbol: modelData !== undefined && !!modelData ? RootStore.findTokenSymbolByAddress(modelData.contract) : ""
transferStatus: modelData !== undefined ? RootStore.hex2Dec(modelData.txStatus) : "" transferStatus: modelData !== undefined && !!modelData ? RootStore.hex2Dec(modelData.txStatus) : ""
shortTimeStamp: modelData !== undefined ? Utils.formatShortTime(modelData.timestamp * 1000, RootStore.accountSensitiveSettings.is24hTimeFormat) : "" shortTimeStamp: modelData !== undefined && !!modelData ? Utils.formatShortTime(modelData.timestamp * 1000, RootStore.accountSensitiveSettings.is24hTimeFormat) : ""
savedAddressName: modelData !== undefined ? RootStore.getNameForSavedWalletAddress(modelData.to) : "" savedAddressName: modelData !== undefined && !!modelData ? RootStore.getNameForSavedWalletAddress(modelData.to) : ""
onClicked: { onClicked: launchTransactionDetail(modelData)
transactionModal.transaction = modelData
transactionModal.open()
}
} }
} }
@ -131,8 +130,4 @@ ColumnLayout {
id: loadingImageComponent id: loadingImageComponent
StatusLoadingIndicator {} StatusLoadingIndicator {}
} }
TransactionModal {
id: transactionModal
}
} }

View File

@ -0,0 +1,250 @@
import QtQuick 2.13
import QtQuick.Layouts 1.13
import QtQuick.Controls 2.14
import QtQuick.Window 2.12
import StatusQ.Components 0.1
import StatusQ.Core.Theme 0.1
import StatusQ.Core 0.1
import StatusQ.Controls 0.1
import StatusQ.Popups 0.1
import shared.controls 1.0
import utils 1.0
import "../stores"
import "../controls"
Item {
id: root
property var currentAccount: RootStore.currentAccount
property var contactsStore
property var transaction
property var sendModal
signal goBack()
QtObject {
id: d
readonly property bool isIncoming: root.transaction !== undefined && !!root.transaction ? root.transaction.to === currentAccount.address : false
readonly property string savedAddressNameTo: root.transaction !== undefined && !!root.transaction ? d.getNameForSavedWalletAddress(transaction.to) : ""
readonly property string savedAddressNameFrom: root.transaction !== undefined && !!root.transaction ? d.getNameForSavedWalletAddress(transaction.from): ""
readonly property string from: root.transaction !== undefined && !!root.transaction ? !!savedAddressNameFrom ? savedAddressNameFrom : Utils.compactAddress(transaction.from, 4): ""
readonly property string to: root.transaction !== undefined && !!root.transaction ? !!savedAddressNameTo ? savedAddressNameTo : Utils.compactAddress(transaction.to, 4): ""
function getNameForSavedWalletAddress(address) {
return RootStore.getNameForSavedWalletAddress(address)
}
}
StatusFlatButton {
id: backButton
anchors.top: parent.top
anchors.left: parent.left
Layout.alignment: Qt.AlignTop
anchors.topMargin: -Style.current.xlPadding
anchors.leftMargin: -Style.current.xlPadding
icon.name: "arrow-left"
icon.width: 20
icon.height: 20
text: qsTr("Activity")
size: StatusBaseButton.Size.Large
onClicked: root.goBack()
}
StatusScrollView {
anchors.top: backButton.bottom
anchors.left: parent.left
width: parent.width
height: parent.height
contentHeight: column.height
contentWidth: parent.width
Column {
id: column
width: parent.width - Style.current.xlPadding
spacing: Style.current.bigPadding
TransactionDelegate {
width: parent.width
modelData: transaction
isIncoming: d.isIncoming
currentCurrency: RootStore.currentCurrency
cryptoValue: root.transaction !== undefined && !!root.transaction ? RootStore.hex2Eth(transaction.value): ""
fiatValue: root.transaction !== undefined && !!root.transaction ? RootStore.getFiatValue(cryptoValue, resolvedSymbol, RootStore.currentCurrency): ""
networkIcon: root.transaction !== undefined && !!root.transaction ? RootStore.getNetworkIcon(transaction.chainId): ""
networkColor: root.transaction !== undefined && !!root.transaction ? RootStore.getNetworkColor(transaction.chainId): ""
networkName: root.transaction !== undefined && !!root.transaction ? RootStore.getNetworkShortName(transaction.chainId): ""
symbol: root.transaction !== undefined && !!root.transaction ? RootStore.findTokenSymbolByAddress(transaction.contract): ""
transferStatus: root.transaction !== undefined && !!root.transaction ? RootStore.hex2Dec(transaction.txStatus): ""
shortTimeStamp: root.transaction !== undefined && !!root.transaction ? Utils.formatShortTime(transaction.timestamp * 1000, RootStore.accountSensitiveSettings.is24hTimeFormat): ""
savedAddressName: root.transaction !== undefined && !!root.transaction ? RootStore.getNameForSavedWalletAddress(transaction.to): ""
title: d.isIncoming ? qsTr("Received %1 %2 from %3").arg(cryptoValue).arg(resolvedSymbol).arg(d.from) :
qsTr("Sent %1 %2 to %3").arg(cryptoValue).arg(resolvedSymbol).arg(d.to)
sensor.enabled: false
color: Theme.palette.statusListItem.backgroundColor
state: "big"
}
SavedAddressesDelegate {
width: parent.width
name: d.isIncoming ? d.savedAddressNameFrom : d.savedAddressNameTo
address: root.transaction !== undefined && !!root.transaction ? d.isIncoming ? transaction.from : transaction.to : ""
title: d.isIncoming ? d.from : d.to
subTitle: root.transaction !== undefined && !!root.transaction ? d.isIncoming ? !!d.savedAddressNameFrom ? Utils.compactAddress(transaction.from, 4) : "" : !!d.savedAddressNameTo ? Utils.compactAddress(transaction.to, 4) : "": ""
store: RootStore
contactsStore: root.contactsStore
onOpenSendModal: root.sendModal.open(address);
saveAddress: function(name, address) {
RootStore.createOrUpdateSavedAddress(name, address)
}
deleteSavedAddress: function(address) {
RootStore.deleteSavedAddress(address)
}
}
StatusExpandableItem {
width: parent.width
anchors.horizontalCenter: parent.horizontalCenter
type: StatusExpandableItem.Type.Tertiary
expandable: true
primaryText: qsTr("Transaction summary")
expandableComponent: transactionSummary
separatorVisible: false
expanded: true
}
StatusExpandableItem {
width: parent.width
anchors.horizontalCenter: parent.horizontalCenter
type: StatusExpandableItem.Type.Tertiary
expandable: true
primaryText: qsTr("Fees")
expandableComponent: fees
expanded: true
}
StatusListItem {
id: data
width: parent.width
anchors.horizontalCenter: parent.horizontalCenter
color: "transparent"
border.width: 1
border.color: Theme.palette.directColor8
statusListItemTitle.color: Theme.palette.baseColor1
title: qsTr("Data" )
subTitle: root.transaction !== undefined && !!root.transaction ? root.transaction.input : ""
components: [
CopyToClipBoardButton {
icon.width: 15
icon.height: 15
type: StatusRoundButton.Type.Tertiary
color: "transparent"
icon.color: data.showButtons ? Theme.palette.directColor1 : Theme.palette.baseColor1
store: RootStore
textToCopy: data.subTitle
}
]
}
}
}
Component {
id: transactionSummary
Column {
id: column
width: parent.width
spacing: 8
TransactionDelegate {
width: parent.width
modelData: transaction
isIncoming: d.isIncoming
currentCurrency: RootStore.currentCurrency
cryptoValue: root.transaction !== undefined && !!root.transaction ? RootStore.hex2Eth(transaction.value): ""
fiatValue: RootStore.getFiatValue(cryptoValue, resolvedSymbol, RootStore.currentCurrency)
networkIcon: root.transaction !== undefined && !!root.transaction ? RootStore.getNetworkIcon(transaction.chainId) : ""
networkColor: root.transaction !== undefined && !!root.transaction ? RootStore.getNetworkColor(transaction.chainId): ""
networkName: root.transaction !== undefined && !!root.transaction ? RootStore.getNetworkShortName(transaction.chainId): ""
symbol: root.transaction !== undefined && !!root.transaction ? RootStore.findTokenSymbolByAddress(transaction.contract): ""
transferStatus: root.transaction !== undefined && !!root.transaction ? RootStore.hex2Dec(transaction.txStatus): ""
shortTimeStamp: root.transaction !== undefined && !!root.transaction ? Utils.formatShortTime(transaction.timestamp * 1000, RootStore.accountSensitiveSettings.is24hTimeFormat): ""
savedAddressName: root.transaction !== undefined && !!root.transaction ? RootStore.getNameForSavedWalletAddress(transaction.to): ""
title: d.isIncoming ? qsTr("Received %1 %2 from %3").arg(cryptoValue).arg(resolvedSymbol).arg(d.from) :
qsTr("Sent %1 %2 to %3").arg(cryptoValue).arg(resolvedSymbol).arg(d.to)
sensor.enabled: false
color: Theme.palette.statusListItem.backgroundColor
border.width: 1
border.color: Theme.palette.directColor8
}
Row {
spacing: 8
InformationTile {
maxWidth: parent.width
primaryText: qsTr("Time")
secondaryText: root.transaction !== undefined && !!root.transaction ? qsTr("%1 <font color=\"#939BA1\">on</font> %2").
arg(Utils.formatShortTime(transaction.timestamp * 1000, RootStore.accountSensitiveSettings.is24hTimeFormat)).
arg(Utils.formatShortDate(transaction.timestamp * 1000, RootStore.accountSensitiveSettings.is24hTimeFormat)): ""
}
InformationTile {
maxWidth: parent.width
primaryText: qsTr("Confirmations")
secondaryText: {
if(root.transaction !== undefined && !!root.transaction )
return Math.abs(RootStore.getLatestBlockNumber() - RootStore.hex2Dec(root.transaction.blockNumber))
else
return ""
}
}
InformationTile {
maxWidth: parent.width
primaryText: qsTr("Nonce")
secondaryText: root.transaction !== undefined && !!root.transaction ? RootStore.hex2Dec(root.transaction.nonce) : ""
}
}
}
}
Component {
id: fees
Column {
width: parent.width
spacing: 8
Row {
spacing: 8
InformationTile {
id: baseFee
maxWidth: parent.width
primaryText: qsTr("Base fee")
secondaryText: root.transaction !== undefined && !!root.transaction ? qsTr("%1 Gwei").arg(RootStore.hex2Gwei(root.transaction.baseGasFees)) : ""
}
InformationTile {
maxWidth: parent.width
primaryText: qsTr("Tip")
secondaryText: root.transaction !== undefined && !!root.transaction ? qsTr("%1 Gwei <font color=\"#939BA1\">&#8226; Max: %2 Gwei</font>").
arg(RootStore.hex2Gwei(root.transaction.maxPriorityFeePerGas)).
arg(RootStore.hex2Gwei(root.transaction.maxFeePerGas)) : ""
secondaryLabel.textFormat: Text.RichText
}
}
InformationTile {
maxWidth: parent.width
primaryText: qsTr("Total fee")
secondaryText: root.transaction !== undefined && !!root.transaction ? qsTr("%1 Gwei <font color=\"#939BA1\">&#8226; Max: %2 Gwei</font>").
arg(Utils.stripTrailingZeros(RootStore.hex2Gwei(root.transaction.totalFees))).
arg(Utils.stripTrailingZeros(RootStore.hex2Gwei(root.transaction.maxTotalFees))) : ""
secondaryLabel.textFormat: Text.RichText
}
}
}
}

View File

@ -11,3 +11,4 @@ ProfileView 1.0 ProfileView.qml
AssetsView 1.0 AssetsView.qml AssetsView 1.0 AssetsView.qml
HistoryView 1.0 HistoryView.qml HistoryView 1.0 HistoryView.qml
AssetsDetailView 1.0 AssetsDetailView.qml AssetsDetailView 1.0 AssetsDetailView.qml
TransactionDetailView 1.0 TransactionDetailView.qml

2
vendor/status-go vendored

@ -1 +1 @@
Subproject commit 65be6f2b96d72161bfa23a384e9d0f2ccfc85c6a Subproject commit 1485b3b4c808dd875d30f17e4be842fcc44c8d35