feat: enable sending chat requests

This commit is contained in:
Jonathan Rainville 2020-09-03 16:14:44 -04:00 committed by Iuri Matias
parent 60492b4db1
commit 6b3cac31bc
16 changed files with 513 additions and 27 deletions

View File

@ -18,6 +18,10 @@ proc handleChatEvents(self: ChatController) =
self.view.updateUsernames(evArgs.contacts)
self.view.updateChats(evArgs.chats)
self.view.pushMessages(evArgs.messages)
for message in evArgs.messages:
if (message.replace != ""):
# Delete the message taht this message replaces
self.view.deleteMessage(message.chatId, message.replace)
self.view.pushReactions(evArgs.emojiReactions)
self.status.events.on("channelUpdate") do(e: Args):

View File

@ -585,3 +585,9 @@ QtObject:
proc declineRequestTransaction*(self: ChatsView, messageId: string) {.slot.} =
self.status.chat.declineRequestTransaction(messageId)
proc requestAddressForTransaction*(self: ChatsView, chatId: string, fromAddress: string, amount: string, tokenAddress: string) {.slot.} =
self.status.chat.requestAddressForTransaction(chatId, fromAddress, amount, tokenAddress)
proc requestTransaction*(self: ChatsView, chatId: string, fromAddress: string, amount: string, tokenAddress: string) {.slot.} =
self.status.chat.requestTransaction(chatId, fromAddress, amount, tokenAddress)

View File

@ -71,6 +71,7 @@ QtObject:
result.setup
proc deleteMessage*(self: ChatMessageList, messageId: string) =
if not self.messageIndex.hasKey(messageId): return
let messageIndex = self.messageIndex[messageId]
self.beginRemoveRows(newQModelIndex(), messageIndex, messageIndex)
self.messages.delete(messageIndex)

View File

@ -248,6 +248,21 @@ QtObject:
proc defaultCurrency*(self: WalletView): string {.slot.} =
self.status.wallet.getDefaultCurrency()
proc getAccountValueByAddress*(self: WalletView, address: string, arg: string): string {.slot.} =
let index = self.accounts.getAccountindexByAddress(address)
if index == -1: return
let account = self.accounts.getAccount(index)
case arg:
of "name": result = account.name
of "iconColor": result = account.iconColor
of "balance": result = account.balance
of "path": result = account.path
of "walletType": result = account.walletType
of "publicKey": result = account.publicKey
of "realFiatBalance": result = $account.realFiatBalance
of "wallet": result = $account.wallet
of "chat": result = $account.wallet
proc defaultCurrencyChanged*(self: WalletView) {.signal.}
proc setDefaultCurrency*(self: WalletView, currency: string) {.slot.} =

View File

@ -4,6 +4,7 @@ import libstatus/chat as status_chat
import libstatus/mailservers as status_mailservers
import libstatus/chatCommands as status_chat_commands
import libstatus/stickers as status_stickers
import libstatus/accounts/constants as constants
import libstatus/types
import mailservers
import profile/profile
@ -411,7 +412,16 @@ proc declineRequestAddressForTransaction*(self: ChatModel, messageId: string) =
let response = status_chat_commands.declineRequestAddressForTransaction(messageId)
self.processUpdateForTransaction(messageId, response)
proc declineRequestTransaction*(self: ChatModel, messageId: string) =
let response = status_chat_commands.declineRequestTransaction(messageId)
self.processUpdateForTransaction(messageId, response)
proc requestAddressForTransaction*(self: ChatModel, chatId: string, fromAddress: string, amount: string, tokenAddress: string) =
let address = if (tokenAddress == constants.ZERO_ADDRESS): "" else: tokenAddress
let response = status_chat_commands.requestAddressForTransaction(chatId, fromAddress, amount, address)
discard self.processMessageUpdateAfterSend(response)
proc requestTransaction*(self: ChatModel, chatId: string, fromAddress: string, amount: string, tokenAddress: string) =
let address = if (tokenAddress == constants.ZERO_ADDRESS): "" else: tokenAddress
let response = status_chat_commands.requestTransaction(chatId, fromAddress, amount, address)
discard self.processMessageUpdateAfterSend(response)

View File

@ -44,7 +44,7 @@ type Message* = object
messageType*: string # ???
parsedText*: seq[TextItem]
# quotedMessage: # ???
replace*: string # ???
replace*: string
responseTo*: string
rtl*: bool # ???
seen*: bool # ???

View File

@ -9,3 +9,9 @@ proc declineRequestAddressForTransaction*(messageId: string): string =
proc declineRequestTransaction*(messageId: string): string =
result = callPrivateRPC("declineRequestTransaction".prefix, %* [messageId])
proc requestAddressForTransaction*(chatId: string, fromAddress: string, amount: string, tokenAddress: string): string =
result = callPrivateRPC("requestAddressForTransaction".prefix, %* [chatId, fromAddress, amount, tokenAddress])
proc requestTransaction*(chatId: string, fromAddress: string, amount: string, tokenAddress: string): string =
result = callPrivateRPC("requestTransaction".prefix, %* [chatId, amount, tokenAddress, fromAddress])

View File

@ -0,0 +1,193 @@
import QtQuick 2.13
import QtQuick.Controls 2.13
import QtQuick.Layouts 1.13
import QtQuick.Dialogs 1.3
import "../../../../../imports"
import "../../../../../shared"
import "../../../../../shared/status"
ModalPopup {
property string commandTitle: "Send"
property string finalButtonLabel: "Request address"
property var sendChatCommand: function () {}
id: root
title: root.commandTitle
height: 504
property var selectedRecipient
onSelectedRecipientChanged: {
selectRecipient.selectedRecipient = this.selectedRecipient
selectRecipient.readOnly = !!this.selectedRecipient && !!this.selectedRecipient.address
}
onClosed: {
stack.reset()
}
TransactionStackView {
id: stack
anchors.fill: parent
anchors.leftMargin: Style.current.padding
anchors.rightMargin: Style.current.padding
onGroupActivated: {
root.title = group.headerText
btnNext.label = group.footerText
}
TransactionFormGroup {
id: group1
headerText: root.commandTitle
footerText: qsTr("Continue")
AccountSelector {
id: selectFromAccount
accounts: walletModel.accounts
selectedAccount: walletModel.currentAccount
currency: walletModel.defaultCurrency
width: stack.width
label: qsTr("From account")
reset: function() {
accounts = Qt.binding(function() { return walletModel.accounts })
selectedAccount = Qt.binding(function() { return walletModel.currentAccount })
}
}
SeparatorWithIcon {
id: separator
anchors.top: selectFromAccount.bottom
anchors.topMargin: 19
}
RecipientSelector {
id: selectRecipient
accounts: walletModel.accounts
contacts: profileModel.addedContacts
label: qsTr("Recipient")
readOnly: true
selectedRecipient: root.selectedRecipient
anchors.top: separator.bottom
anchors.topMargin: 10
width: stack.width
reset: function() {
isValid = true
}
}
}
TransactionFormGroup {
id: group2
headerText: root.commandTitle
footerText: qsTr("Preview")
AssetAndAmountInput {
id: txtAmount
selectedAccount: selectFromAccount.selectedAccount
defaultCurrency: walletModel.defaultCurrency
getFiatValue: walletModel.getFiatValue
getCryptoValue: walletModel.getCryptoValue
width: stack.width
reset: function() {
selectedAccount = Qt.binding(function() { return selectFromAccount.selectedAccount })
}
}
}
TransactionFormGroup {
id: group3
headerText: qsTr("Transaction preview")
footerText: root.finalButtonLabel
TransactionPreview {
id: pvwTransaction
width: stack.width
fromAccount: selectFromAccount.selectedAccount
toAccount: selectRecipient.selectedRecipient
asset: txtAmount.selectedAsset
amount: { "value": txtAmount.selectedAmount, "fiatValue": txtAmount.selectedFiatAmount }
currency: walletModel.defaultCurrency
reset: function() {
fromAccount = Qt.binding(function() { return selectFromAccount.selectedAccount })
toAccount = Qt.binding(function() { return selectRecipient.selectedRecipient })
asset = Qt.binding(function() { return txtAmount.selectedAsset })
amount = Qt.binding(function() { return { "value": txtAmount.selectedAmount, "fiatValue": txtAmount.selectedFiatAmount } })
}
}
SVGImage {
width: 16
height: 16
source: "../../../../img/warning.svg"
anchors.horizontalCenter: parent.horizontalCenter
anchors.bottom: warningText.top
anchors.bottomMargin: 4
}
StyledText {
id: warningText
text: qsTr("You need to request the recipients address first.\nAssets wont be sent yet.")
color: Style.current.danger
horizontalAlignment: Text.AlignHCenter
wrapMode: Text.WordWrap
anchors.right: parent.right
anchors.left: parent.left
anchors.bottom: parent.bottom
}
}
}
footer: Item {
anchors.top: parent.top
anchors.left: parent.left
anchors.right: parent.right
StyledButton {
id: btnBack
anchors.left: parent.left
width: 44
height: 44
visible: !stack.isFirstGroup
label: ""
background: Rectangle {
anchors.fill: parent
border.width: 0
radius: width / 2
color: btnBack.disabled ? Style.current.grey :
btnBack.hovered ? Qt.darker(btnBack.btnColor, 1.1) : btnBack.btnColor
SVGImage {
width: 20.42
height: 15.75
anchors.horizontalCenter: parent.horizontalCenter
anchors.verticalCenter: parent.verticalCenter
fillMode: Image.PreserveAspectFit
source: "../../../../img/arrow-right.svg"
rotation: 180
}
}
onClicked: {
stack.back()
}
}
StyledButton {
id: btnNext
anchors.right: parent.right
label: qsTr("Next")
disabled: !stack.currentGroup.isValid
onClicked: {
const isValid = stack.currentGroup.validate()
if (stack.currentGroup.validate()) {
if (stack.isLastGroup) {
return root.sendChatCommand(selectFromAccount.selectedAccount.address,
txtAmount.selectedAmount,
txtAmount.selectedAsset.address)
}
stack.next()
}
}
}
}
}
/*##^##
Designer {
D{i:0;autoSize:true;height:480;width:640}
}
##^##*/

View File

@ -3,7 +3,6 @@ import QtQuick.Controls 2.13
import QtGraphicalEffects 1.13
import "../../../../../imports"
import "../../../../../shared"
import "../../../Wallet"
Popup {
id: root
@ -42,13 +41,24 @@ Popup {
//% "Send transaction"
text: qsTrId("send-transaction")
onClicked: function () {
sendModal.selectedRecipient = {
address: "0x9ce0056c5fc6bb9459a4dcfa35eaad8c1fee5ce9",
chatCommandModal.commandTitle = qsTr("Send")
chatCommandModal.title = chatCommandModal.commandTitle
chatCommandModal.finalButtonLabel = qsTr("Request Address")
chatCommandModal.selectedRecipient = {
address: Constants.zeroAddress, // Setting as zero address since we don't have the address yet
identicon: chatsModel.activeChannel.identicon,
name: chatsModel.activeChannel.name,
type: RecipientSelector.Type.Contact
}
sendModal.open()
chatCommandModal.sendChatCommand = function(address, amount, tokenAddress) {
chatsModel.requestAddressForTransaction(chatsModel.activeChannel.id,
address,
amount,
tokenAddress)
chatCommandModal.close()
}
chatCommandModal.open()
root.close()
}
}
@ -58,21 +68,30 @@ Popup {
rotatedImage: true
//% "Request transaction"
text: qsTrId("request-transaction")
}
SendModal {
id: sendModal
onOpened: {
walletModel.getGasPricePredictions()
}
selectedRecipient: {
return {
address: "0x9ce0056c5fc6bb9459a4dcfa35eaad8c1fee5ce9",
onClicked: function () {
chatCommandModal.commandTitle = qsTr("Request")
chatCommandModal.title = chatCommandModal.commandTitle
chatCommandModal.finalButtonLabel = qsTr("Request")
chatCommandModal.selectedRecipient = {
address: Constants.zeroAddress, // Setting as zero address since we don't have the address yet
identicon: chatsModel.activeChannel.identicon,
name: chatsModel.activeChannel.name,
type: RecipientSelector.Type.Contact
}
}
chatCommandModal.sendChatCommand = function(address, amount, tokenAddress) {
chatsModel.requestTransaction(chatsModel.activeChannel.id,
address,
amount,
tokenAddress)
chatCommandModal.close()
}
chatCommandModal.open()
root.close()
}
}
ChatCommandModal {
id: chatCommandModal
}
}
}

View File

@ -0,0 +1,190 @@
import QtQuick 2.13
import QtQuick.Controls 2.13
import QtQuick.Layouts 1.13
import QtQuick.Dialogs 1.3
import "../../../../../imports"
import "../../../../../shared"
import "../../../../../shared/status"
ModalPopup {
property var selectedAccount
property var selectedRecipient
property var selectedAsset
property var selectedAmount
property var selectedFiatAmount
property var selectedGasLimit
property var selectedGasPrice
id: root
//% "Send"
title: qsTrId("command-button-send")
height: 504
property MessageDialog sendingError: MessageDialog {
id: sendingError
title: qsTr("Error sending the transaction")
icon: StandardIcon.Critical
standardButtons: StandardButton.Ok
}
property MessageDialog sendingSuccess: MessageDialog {
id: sendingSuccess
//% "Success sending the transaction"
title: qsTrId("success-sending-the-transaction")
icon: StandardIcon.NoIcon
standardButtons: StandardButton.Ok
onAccepted: {
root.close()
}
}
onClosed: {
stack.reset()
}
function sendTransaction() {
let responseStr = walletModel.sendTransaction(root.selectedAccount.address,
root.selectedRecipient.address,
root.selectedAsset.address,
root.selectedAmount,
gasSelector.selectedGasLimit,
gasSelector.selectedGasPrice,
transactionSigner.enteredPassword)
let response = JSON.parse(responseStr)
if (response.error) {
if (response.error.includes("could not decrypt key with given password")){
transactionSigner.validationError = qsTr("Wrong password")
return
}
sendingError.text = response.error
return sendingError.open()
}
sendingSuccess.text = qsTr("Transaction sent to the blockchain. You can watch the progress on Etherscan: %2/%1").arg(response.result).arg(walletModel.etherscanLink)
sendingSuccess.open()
}
TransactionStackView {
id: stack
anchors.fill: parent
anchors.leftMargin: Style.current.padding
anchors.rightMargin: Style.current.padding
onGroupActivated: {
root.title = group.headerText
btnNext.label = group.footerText
}
TransactionFormGroup {
id: group1
headerText: qsTr("Send")
footerText: qsTr("Preview")
GasSelector {
id: gasSelector
anchors.topMargin: Style.current.bigPadding
slowestGasPrice: parseFloat(walletModel.safeLowGasPrice)
fastestGasPrice: parseFloat(walletModel.fastestGasPrice)
getGasEthValue: walletModel.getGasEthValue
getFiatValue: walletModel.getFiatValue
defaultCurrency: walletModel.defaultCurrency
width: stack.width
reset: function() {
slowestGasPrice = Qt.binding(function(){ return parseFloat(walletModel.safeLowGasPrice) })
fastestGasPrice = Qt.binding(function(){ return parseFloat(walletModel.fastestGasPrice) })
}
}
}
TransactionFormGroup {
id: group2
headerText: qsTr("Transaction preview")
footerText: qsTr("Sign with password")
TransactionPreview {
id: pvwTransaction
width: stack.width
fromAccount: root.selectedAccount
gas: {
const value = walletModel.getGasEthValue(gasSelector.selectedGasPrice, gasSelector.selectedGasLimit)
const fiatValue = walletModel.getFiatValue(value, "ETH", walletModel.defaultCurrency)
return { value, "symbol": "ETH", fiatValue }
}
toAccount: root.selectedRecipient
asset: root.selectedAsset
amount: { "value": root.selectedAmount, "fiatValue": root.selectedFiatAmount }
currency: walletModel.defaultCurrency
reset: function() {}
}
}
TransactionFormGroup {
id: group3
headerText: qsTr("Sign with password")
footerText: qsTr("Send %1 %2").arg(root.selectedAmount).arg(!!root.selectedAsset ? root.selectedAsset.symbol : "")
TransactionSigner {
id: transactionSigner
width: stack.width
signingPhrase: walletModel.signingPhrase
reset: function() {
signingPhrase = Qt.binding(function() { return walletModel.signingPhrase })
}
}
}
}
footer: Item {
anchors.top: parent.top
anchors.left: parent.left
anchors.right: parent.right
StyledButton {
id: btnBack
anchors.left: parent.left
width: 44
height: 44
visible: !stack.isFirstGroup
label: ""
background: Rectangle {
anchors.fill: parent
border.width: 0
radius: width / 2
color: btnBack.disabled ? Style.current.grey :
btnBack.hovered ? Qt.darker(btnBack.btnColor, 1.1) : btnBack.btnColor
SVGImage {
width: 20.42
height: 15.75
anchors.horizontalCenter: parent.horizontalCenter
anchors.verticalCenter: parent.verticalCenter
fillMode: Image.PreserveAspectFit
source: "../../../../img/arrow-right.svg"
rotation: 180
}
}
onClicked: {
stack.back()
}
}
StyledButton {
id: btnNext
anchors.right: parent.right
label: qsTr("Next")
disabled: !stack.currentGroup.isValid
onClicked: {
const isValid = stack.currentGroup.validate()
if (stack.currentGroup.validate()) {
if (stack.isLastGroup) {
return root.sendTransaction()
}
stack.next()
}
}
}
}
}
/*##^##
Designer {
D{i:0;autoSize:true;height:480;width:640}
}
##^##*/

View File

@ -7,16 +7,14 @@ import "../../../Wallet/data"
Item {
property var commandParametersObject: {
try {
var result = JSON.parse(commandParameters)
return result
return JSON.parse(commandParameters)
} catch (e) {
console.error('Error parsing command parameters')
console.error('JSON:', commandParameters)
console.error('Error:', e)
return {
id: "",
from: "",
fromAddress: "",
address: "",
contract: "",
value: "",
@ -31,7 +29,7 @@ Item {
return {
symbol: "ETH",
name: "Ethereum",
address: "0x0000000000000000000000000000000000000000",
address: Constants.zeroAddress,
decimals: 18,
hasIcon: true
}

View File

@ -1,6 +1,7 @@
import QtQuick 2.3
import "../../../../../../shared"
import "../../../../../../imports"
import "../../ChatComponents"
Item {
width: parent.width
@ -27,10 +28,41 @@ Item {
anchors.fill: parent
cursorShape: Qt.PointingHandCursor
onClicked: {
console.log('Sign')
signTransactionModal.open()
}
}
}
SignTransactionModal {
id: signTransactionModal
onOpened: {
walletModel.getGasPricePredictions()
}
selectedAccount: {
return {
name: walletModel.getAccountValueByAddress(commandParametersObject.fromAddress, 'name'),
address: commandParametersObject.fromAddress,
iconColor: walletModel.getAccountValueByAddress(commandParametersObject.fromAddress, 'iconColor')
}
}
selectedRecipient: {
return {
address: commandParametersObject.address,
identicon: chatsModel.activeChannel.identicon,
name: chatsModel.activeChannel.name,
type: RecipientSelector.Type.Contact
}
}
selectedAsset: {
return {
name: token.name,
symbol: token.symbol,
address: commandParametersObject.contract
}
}
selectedAmount: tokenAmount
selectedFiatAmount: fiatValue
}
}
/*##^##

5
ui/app/img/warning.svg Normal file
View File

@ -0,0 +1,5 @@
<svg width="16" height="16" viewBox="0 0 16 16" fill="none" xmlns="http://www.w3.org/2000/svg">
<path d="M7.99992 4.16671C8.27606 4.16671 8.49992 4.39057 8.49992 4.66671V9.33337C8.49992 9.60952 8.27606 9.83337 7.99992 9.83337C7.72378 9.83337 7.49992 9.60952 7.49992 9.33337V4.66671C7.49992 4.39057 7.72378 4.16671 7.99992 4.16671Z" fill="#FF2D55"/>
<path d="M7.99992 12.5C8.46016 12.5 8.83325 12.1269 8.83325 11.6667C8.83325 11.2065 8.46016 10.8334 7.99992 10.8334C7.53968 10.8334 7.16659 11.2065 7.16659 11.6667C7.16659 12.1269 7.53968 12.5 7.99992 12.5Z" fill="#FF2D55"/>
<path fill-rule="evenodd" clip-rule="evenodd" d="M1.33325 8.00004C1.33325 11.6819 4.31802 14.6667 7.99992 14.6667C11.6818 14.6667 14.6666 11.6819 14.6666 8.00004C14.6666 4.31814 11.6818 1.33337 7.99992 1.33337C4.31802 1.33337 1.33325 4.31814 1.33325 8.00004ZM2.33325 8.00004C2.33325 11.1297 4.8703 13.6667 7.99992 13.6667C11.1295 13.6667 13.6666 11.1297 13.6666 8.00004C13.6666 4.87043 11.1295 2.33337 7.99992 2.33337C4.8703 2.33337 2.33325 4.87043 2.33325 8.00004Z" fill="#FF2D55"/>
</svg>

After

Width:  |  Height:  |  Size: 1.0 KiB

View File

@ -33,6 +33,8 @@ QtObject {
readonly property int pending: 6
readonly property int confirmed: 7
readonly property string zeroAddress: "0x0000000000000000000000000000000000000000"
readonly property var accountColors: [
"#9B832F",
"#D37EF4",

View File

@ -116,8 +116,11 @@ else: unix:!android: target.path = /opt/$${TARGET}/bin
DISTFILES += \
app/AppLayouts/Chat/ChatColumn/ChatComponents/ChatCommandButton.qml \
app/AppLayouts/Chat/ChatColumn/ChatComponents/ChatCommandModal.qml \
app/AppLayouts/Chat/ChatColumn/ChatComponents/ChatCommandsPopup.qml \
app/AppLayouts/Chat/ChatColumn/ChatComponents/ChatInputButton.qml \
app/AppLayouts/Chat/ChatColumn/ChatComponents/RequestModal.qml \
app/AppLayouts/Chat/ChatColumn/ChatComponents/SignTransactionModal.qml \
app/AppLayouts/Chat/ChatColumn/CompactMessage.qml \
app/AppLayouts/Chat/ChatColumn/MessageComponents/ChannelIdentifier.qml \
app/AppLayouts/Chat/ChatColumn/MessageComponents/ChatImage.qml \

View File

@ -21,7 +21,7 @@ Item {
property var getFiatValue: function () {}
property var getCryptoValue: function () {}
property bool isDirty: false
property bool isValid: false
property bool isValid: true
property var reset: function() {}
function resetInternal() {
@ -31,7 +31,7 @@ Item {
inputAmount.resetInternal()
txtBalanceDesc.color = Style.current.secondaryText
txtBalance.color = Qt.binding(function() { return txtBalance.hovered ? Style.current.textColor : Style.current.secondaryText })
isValid = false
isValid = true
}
id: root
@ -41,6 +41,8 @@ Item {
anchors.left: parent.left
function validate(checkDirty) {
// TODO remove me
return true
let isValid = true
let error = ""
const hasTyped = checkDirty ? isDirty : true