feat: add chat command bubbles for received txs

This commit is contained in:
Jonathan Rainville 2020-09-03 16:43:08 -04:00 committed by Iuri Matias
parent 4e801c5336
commit 60492b4db1
24 changed files with 1909 additions and 192 deletions

View File

@ -24,6 +24,10 @@ proc handleChatEvents(self: ChatController) =
var evArgs = ChatUpdateArgs(e) var evArgs = ChatUpdateArgs(e)
self.view.updateChats(evArgs.chats, false) self.view.updateChats(evArgs.chats, false)
self.status.events.on("messageDeleted") do(e: Args):
var evArgs = MessageArgs(e)
self.view.deleteMessage(evArgs.channel, evArgs.id)
self.status.events.on("chatHistoryCleared") do(e: Args): self.status.events.on("chatHistoryCleared") do(e: Args):
var args = ChannelArgs(e) var args = ChannelArgs(e)
self.view.clearMessages(args.chat.id) self.view.clearMessages(args.chat.id)

View File

@ -488,6 +488,9 @@ QtObject:
self.currentSuggestions.setNewData(self.status.contacts.getContacts()) self.currentSuggestions.setNewData(self.status.contacts.getContacts())
self.calculateUnreadMessages() self.calculateUnreadMessages()
proc deleteMessage*(self: ChatsView, channelId: string, messageId: string) =
self.messageList[channelId].deleteMessage(messageId)
proc renameGroup*(self: ChatsView, newName: string) {.slot.} = proc renameGroup*(self: ChatsView, newName: string) {.slot.} =
self.status.chat.renameGroup(self.activeChannel.id, newName) self.status.chat.renameGroup(self.activeChannel.id, newName)
@ -570,5 +573,15 @@ QtObject:
if (self.chats.chats.len == 0): return false if (self.chats.chats.len == 0): return false
let selectedChannel = self.chats.getChannel(channelIndex) let selectedChannel = self.chats.getChannel(channelIndex)
if (selectedChannel == nil): return false if (selectedChannel == nil): return false
result = selectedChannel.muted result = selectedChannel.muted
### Chat commands functions ###
proc acceptRequestAddressForTransaction*(self: ChatsView, messageId: string , address: string) {.slot.} =
self.status.chat.acceptRequestAddressForTransaction(messageId, address)
proc declineRequestAddressForTransaction*(self: ChatsView, messageId: string) {.slot.} =
self.status.chat.declineRequestAddressForTransaction(messageId)
proc declineRequestTransaction*(self: ChatsView, messageId: string) {.slot.} =
self.status.chat.declineRequestTransaction(messageId)

View File

@ -1,4 +1,4 @@
import NimQml, Tables, sets import NimQml, Tables, sets, json
import ../../../status/status import ../../../status/status
import ../../../status/accounts import ../../../status/accounts
import ../../../status/chat import ../../../status/chat
@ -31,6 +31,7 @@ type
Audio = UserRole + 20 Audio = UserRole + 20
AudioDurationMs = UserRole + 21 AudioDurationMs = UserRole + 21
EmojiReactions = UserRole + 22 EmojiReactions = UserRole + 22
CommandParameters = UserRole + 23
QtObject: QtObject:
type type
@ -69,6 +70,14 @@ QtObject:
result.status = status result.status = status
result.setup result.setup
proc deleteMessage*(self: ChatMessageList, messageId: string) =
let messageIndex = self.messageIndex[messageId]
self.beginRemoveRows(newQModelIndex(), messageIndex, messageIndex)
self.messages.delete(messageIndex)
self.messageIndex.del(messageId)
self.messageReactions.del(messageId)
self.endRemoveRows()
proc resetTimeOut*(self: ChatMessageList, messageId: string) = proc resetTimeOut*(self: ChatMessageList, messageId: string) =
if not self.messageIndex.hasKey(messageId): return if not self.messageIndex.hasKey(messageId): return
let msgIdx = self.messageIndex[messageId] let msgIdx = self.messageIndex[messageId]
@ -126,6 +135,17 @@ QtObject:
of ChatMessageRoles.Audio: result = newQVariant(message.audio) of ChatMessageRoles.Audio: result = newQVariant(message.audio)
of ChatMessageRoles.AudioDurationMs: result = newQVariant(message.audioDurationMs) of ChatMessageRoles.AudioDurationMs: result = newQVariant(message.audioDurationMs)
of ChatMessageRoles.EmojiReactions: result = newQVariant(self.getReactions(message.id)) of ChatMessageRoles.EmojiReactions: result = newQVariant(self.getReactions(message.id))
# Pass the command parameters as a JSON string
of ChatMessageRoles.CommandParameters: result = newQVariant($(%*{
"id": message.commandParameters.id,
"fromAddress": message.commandParameters.fromAddress,
"address": message.commandParameters.address,
"contract": message.commandParameters.contract,
"value": message.commandParameters.value,
"transactionHash": message.commandParameters.transactionHash,
"commandState": message.commandParameters.commandState,
"signature": message.commandParameters.signature
}))
method roleNames(self: ChatMessageList): Table[int, string] = method roleNames(self: ChatMessageList): Table[int, string] =
{ {
@ -150,7 +170,8 @@ QtObject:
ChatMessageRoles.Image.int: "image", ChatMessageRoles.Image.int: "image",
ChatMessageRoles.Audio.int: "audio", ChatMessageRoles.Audio.int: "audio",
ChatMessageRoles.AudioDurationMs.int: "audioDurationMs", ChatMessageRoles.AudioDurationMs.int: "audioDurationMs",
ChatMessageRoles.EmojiReactions.int: "emojiReactions" ChatMessageRoles.EmojiReactions.int: "emojiReactions",
ChatMessageRoles.CommandParameters.int: "commandParameters"
}.toTable }.toTable
proc getMessageIndex(self: ChatMessageList, messageId: string): int {.slot.} = proc getMessageIndex(self: ChatMessageList, messageId: string): int {.slot.} =

View File

@ -2,6 +2,7 @@ import eventemitter, json, strutils, sequtils, tables, chronicles, sugar, times
import libstatus/contracts as status_contracts import libstatus/contracts as status_contracts
import libstatus/chat as status_chat import libstatus/chat as status_chat
import libstatus/mailservers as status_mailservers import libstatus/mailservers as status_mailservers
import libstatus/chatCommands as status_chat_commands
import libstatus/stickers as status_stickers import libstatus/stickers as status_stickers
import libstatus/types import libstatus/types
import mailservers import mailservers
@ -265,19 +266,20 @@ proc clearHistory*(self: ChatModel, chatId: string) =
proc setActiveChannel*(self: ChatModel, chatId: string) = proc setActiveChannel*(self: ChatModel, chatId: string) =
self.events.emit("activeChannelChanged", ChatIdArg(chatId: chatId)) self.events.emit("activeChannelChanged", ChatIdArg(chatId: chatId))
proc sendMessage*(self: ChatModel, chatId: string, msg: string, replyTo: string = "", contentType: int = ContentType.Message.int) = proc processMessageUpdateAfterSend(self: ChatModel, response: string): (seq[Chat], seq[Message]) =
var response = status_chat.sendChatMessage(chatId, msg, replyTo, contentType) result = self.processChatUpdate(parseJson(response))
var (chats, messages) = self.processChatUpdate(parseJson(response)) var (chats, messages) = result
self.events.emit("chatUpdate", ChatUpdateArgs(messages: messages, chats: chats, contacts: @[])) self.events.emit("chatUpdate", ChatUpdateArgs(messages: messages, chats: chats, contacts: @[]))
for msg in messages: for msg in messages:
self.events.emit("sendingMessage", MessageArgs(id: msg.id, channel: msg.chatId)) self.events.emit("sendingMessage", MessageArgs(id: msg.id, channel: msg.chatId))
proc sendMessage*(self: ChatModel, chatId: string, msg: string, replyTo: string = "", contentType: int = ContentType.Message.int) =
var response = status_chat.sendChatMessage(chatId, msg, replyTo, contentType)
discard self.processMessageUpdateAfterSend(response)
proc sendImage*(self: ChatModel, chatId: string, image: string) = proc sendImage*(self: ChatModel, chatId: string, image: string) =
var response = status_chat.sendImageMessage(chatId, image) var response = status_chat.sendImageMessage(chatId, image)
var (chats, messages) = self.processChatUpdate(parseJson(response)) discard self.processMessageUpdateAfterSend(response)
self.events.emit("chatUpdate", ChatUpdateArgs(messages: messages, chats: chats, contacts: @[]))
for msg in messages:
self.events.emit("sendingMessage", MessageArgs(id: msg.id, channel: msg.chatId))
proc addStickerToRecent*(self: ChatModel, sticker: Sticker, save: bool = false) = proc addStickerToRecent*(self: ChatModel, sticker: Sticker, save: bool = false) =
self.recentStickers.insert(sticker, 0) self.recentStickers.insert(sticker, 0)
@ -396,3 +398,20 @@ proc muteChat*(self: ChatModel, chatId: string) =
proc unmuteChat*(self: ChatModel, chatId: string) = proc unmuteChat*(self: ChatModel, chatId: string) =
discard status_chat.unmuteChat(chatId) discard status_chat.unmuteChat(chatId)
proc processUpdateForTransaction*(self: ChatModel, messageId: string, response: string) =
var (chats, messages) = self.processMessageUpdateAfterSend(response)
self.events.emit("messageDeleted", MessageArgs(id: messageId, channel: chats[0].id))
proc acceptRequestAddressForTransaction*(self: ChatModel, messageId: string, address: string) =
let response = status_chat_commands.acceptRequestAddressForTransaction(messageId, address)
self.processUpdateForTransaction(messageId, response)
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)

View File

@ -19,11 +19,21 @@ type TextItem* = object
literal*: string literal*: string
destination*: string destination*: string
type CommandParameters* = object
id*: string
fromAddress*: string
address*: string
contract*: string
value*: string
transactionHash*: string
commandState*: int
signature*: string
type Message* = object type Message* = object
alias*: string alias*: string
chatId*: string chatId*: string
clock*: int clock*: int
# commandParameters*: # ??? commandParameters*: CommandParameters
contentType*: ContentType contentType*: ContentType
ensName*: string ensName*: string
fromAuthor*: string fromAuthor*: string
@ -32,7 +42,7 @@ type Message* = object
lineCount*: int lineCount*: int
localChatId*: string localChatId*: string
messageType*: string # ??? messageType*: string # ???
parsedText*: seq[TextItem] parsedText*: seq[TextItem]
# quotedMessage: # ??? # quotedMessage: # ???
replace*: string # ??? replace*: string # ???
responseTo*: string responseTo*: string

View File

@ -58,7 +58,6 @@ proc chatMessages*(chatId: string, cursor: string = ""): (string, seq[Message])
cursorVal = newJString(cursor) cursorVal = newJString(cursor)
let rpcResult = parseJson(callPrivateRPC("chatMessages".prefix, %* [chatId, cursorVal, 20]))["result"] let rpcResult = parseJson(callPrivateRPC("chatMessages".prefix, %* [chatId, cursorVal, 20]))["result"]
if rpcResult["messages"].kind != JNull: if rpcResult["messages"].kind != JNull:
for jsonMsg in rpcResult["messages"]: for jsonMsg in rpcResult["messages"]:
messages.add(jsonMsg.toMessage) messages.add(jsonMsg.toMessage)

View File

@ -0,0 +1,11 @@
import json, chronicles
import core, utils
proc acceptRequestAddressForTransaction*(messageId: string, address: string): string =
result = callPrivateRPC("acceptRequestAddressForTransaction".prefix, %* [messageId, address])
proc declineRequestAddressForTransaction*(messageId: string): string =
result = callPrivateRPC("declineRequestAddressForTransaction".prefix, %* [messageId])
proc declineRequestTransaction*(messageId: string): string =
result = callPrivateRPC("declineRequestTransaction".prefix, %* [messageId])

View File

@ -198,6 +198,19 @@ proc toMessage*(jsonMsg: JsonNode): Message =
if message.contentType == ContentType.Sticker: if message.contentType == ContentType.Sticker:
message.stickerHash = jsonMsg["sticker"]["hash"].getStr message.stickerHash = jsonMsg["sticker"]["hash"].getStr
.join(" ")
if message.contentType == ContentType.Transaction:
message.commandParameters = CommandParameters(
id: jsonMsg["commandParameters"]["id"].getStr,
fromAddress: jsonMsg["commandParameters"]["from"].getStr,
address: jsonMsg["commandParameters"]["address"].getStr,
contract: jsonMsg["commandParameters"]["contract"].getStr,
value: jsonMsg["commandParameters"]["value"].getStr,
transactionHash: jsonMsg["commandParameters"]["transactionHash"].getStr,
commandState: jsonMsg["commandParameters"]["commandState"].getInt,
signature: jsonMsg["commandParameters"]["signature"].getStr
)
result = message result = message

View File

@ -136,7 +136,6 @@ proc updateAccount*(self: WalletModel, address: string) =
self.events.emit("accountsUpdated", Args()) self.events.emit("accountsUpdated", Args())
proc getTotalFiatBalance*(self: WalletModel): string = proc getTotalFiatBalance*(self: WalletModel): string =
var newBalance = 0.0
fmt"{self.totalBalance:.2f} {self.defaultCurrency}" fmt"{self.totalBalance:.2f} {self.defaultCurrency}"
proc convertValue*(self: WalletModel, balance: string, fromCurrency: string, toCurrency: string): float = proc convertValue*(self: WalletModel, balance: string, fromCurrency: string, toCurrency: string): float =

View File

@ -61,6 +61,8 @@ Rectangle {
MouseArea { MouseArea {
anchors.fill: parent anchors.fill: parent
cursorShape: Qt.PointingHandCursor cursorShape: Qt.PointingHandCursor
onClicked: root.onClicked onClicked: {
root.onClicked()
}
} }
} }

View File

@ -3,51 +3,76 @@ import QtQuick.Controls 2.13
import QtGraphicalEffects 1.13 import QtGraphicalEffects 1.13
import "../../../../../imports" import "../../../../../imports"
import "../../../../../shared" import "../../../../../shared"
import "../../../Wallet"
Popup { Popup {
id: root id: root
width: buttonRow.width width: buttonRow.width
height: buttonRow.height height: buttonRow.height
padding: 0 padding: 0
margins: 0 margins: 0
background: Rectangle { background: Rectangle {
color: Style.current.background color: Style.current.background
radius: Style.current.radius radius: Style.current.radius
border.width: 0 border.width: 0
layer.enabled: true layer.enabled: true
layer.effect: DropShadow{ layer.effect: DropShadow {
verticalOffset: 3 verticalOffset: 3
radius: 8 radius: 8
samples: 15 samples: 15
fast: true fast: true
cached: true cached: true
color: "#22000000" color: "#22000000"
} }
} }
Row { Row {
id: buttonRow id: buttonRow
anchors.left: parent.left anchors.left: parent.left
anchors.leftMargin: 0 anchors.leftMargin: 0
anchors.top: parent.top anchors.top: parent.top
anchors.topMargin: 0 anchors.topMargin: 0
padding: Style.current.halfPadding padding: Style.current.halfPadding
spacing: Style.current.halfPadding spacing: Style.current.halfPadding
ChatCommandButton { ChatCommandButton {
iconColor: Style.current.purple iconColor: Style.current.purple
iconSource: "../../../../img/send.svg" iconSource: "../../../../img/send.svg"
//% "Send transaction" //% "Send transaction"
text: qsTrId("send-transaction") text: qsTrId("send-transaction")
} onClicked: function () {
sendModal.selectedRecipient = {
address: "0x9ce0056c5fc6bb9459a4dcfa35eaad8c1fee5ce9",
identicon: chatsModel.activeChannel.identicon,
name: chatsModel.activeChannel.name,
type: RecipientSelector.Type.Contact
}
sendModal.open()
}
}
ChatCommandButton { ChatCommandButton {
iconColor: Style.current.orange iconColor: Style.current.orange
iconSource: "../../../../img/send.svg" iconSource: "../../../../img/send.svg"
rotatedImage: true rotatedImage: true
//% "Request transaction" //% "Request transaction"
text: qsTrId("request-transaction") text: qsTrId("request-transaction")
} }
}
SendModal {
id: sendModal
onOpened: {
walletModel.getGasPricePredictions()
}
selectedRecipient: {
return {
address: "0x9ce0056c5fc6bb9459a4dcfa35eaad8c1fee5ce9",
identicon: chatsModel.activeChannel.identicon,
name: chatsModel.activeChannel.name,
type: RecipientSelector.Type.Contact
}
}
}
}
} }

View File

@ -79,6 +79,8 @@ Item {
return fetchMoreMessagesButtonComponent return fetchMoreMessagesButtonComponent
case Constants.systemMessagePrivateGroupType: case Constants.systemMessagePrivateGroupType:
return privateGroupHeaderComponent return privateGroupHeaderComponent
case Constants.transactionType:
return transactionBubble
default: default:
return appSettings.compactMode ? compactMessageComponent : messageComponent return appSettings.compactMode ? compactMessageComponent : messageComponent
} }
@ -174,6 +176,12 @@ Item {
clickMessage: messageItem.clickMessage clickMessage: messageItem.clickMessage
} }
} }
// Transaction bubble
Component {
id: transactionBubble
TransactionBubble {}
}
} }
/*##^## /*##^##

View File

@ -2,123 +2,214 @@ import QtQuick 2.3
import "../../../../../shared" import "../../../../../shared"
import "../../../../../imports" import "../../../../../imports"
import "./TransactionComponents" import "./TransactionComponents"
import "../../../Wallet/data"
Rectangle { Item {
property string tokenAmount: "100" property var commandParametersObject: {
property string token: "SNT" try {
property string fiatValue: "10 USD" var result = JSON.parse(commandParameters)
property bool outgoing: true
property string state: "addressReceived" return result
property int timestamp: 1598454756329 } catch (e) {
console.error('Error parsing command parameters')
console.error('JSON:', commandParameters)
console.error('Error:', e)
return {
id: "",
from: "",
address: "",
contract: "",
value: "",
transactionHash: "",
commandState: 1,
signature: null
}
}
}
property var token: {
if (commandParametersObject.contract === "") {
return {
symbol: "ETH",
name: "Ethereum",
address: "0x0000000000000000000000000000000000000000",
decimals: 18,
hasIcon: true
}
}
let count = walletModel.defaultTokenList.items.count
for (let i = 0; i < count; i++) {
let token = walletModel.defaultTokenList.items.get(i)
if (commandParametersObject.contract === token.address) {
return token
}
}
return {}
}
property string tokenAmount: {
if (!commandParametersObject.value) {
return "0"
}
// Divide the Wei value by 10^decimals
var divModResult = Utils.newBigNumber(commandParametersObject.value)
.divmod(Utils.newBigNumber(10)
.pow(token.decimals))
// Make a string with the quotient and the remainder
var str = divModResult.quotient.toString() + "." + divModResult.remainder.toString()
// Remove the useless zeros at the satrt and the end, but keep at least one before the dot
return str.replace(/^(0*)([0-9\.]+?)(0*)$/g, function (match, firstZeros, whatWeKeep, secondZeros) {
if (whatWeKeep.startsWith('.')) {
whatWeKeep = "0" + whatWeKeep
}
return whatWeKeep
})
}
property string tokenSymbol: token.symbol
property string fiatValue: {
if (!tokenAmount || !token.symbol) {
return "0"
}
var defaultFiatSymbol = walletModel.defaultCurrency
return walletModel.getFiatValue(tokenAmount, token.symbol, defaultFiatSymbol) + " " + defaultFiatSymbol.toUpperCase()
}
property int state: commandParametersObject.commandState
property bool outgoing: {
switch (root.state) {
case Constants.pending:
case Constants.confirmed:
case Constants.transactionRequested:
case Constants.addressRequested: return isCurrentUser
case Constants.declined:
case Constants.transactionDeclined:
case Constants.addressReceived: return !isCurrentUser
default: return false
}
}
property int innerMargin: 12
id: root id: root
width: 170 anchors.left: parent.left
height: childrenRect.height anchors.leftMargin: appSettings.compactMode ? Style.current.padding : 48
radius: 16 width: rectangleBubble.width
color: Style.current.background height: rectangleBubble.height
border.color: Style.current.border
border.width: 1
StyledText { Rectangle {
id: title id: rectangleBubble
color: Style.current.secondaryText width: (bubbleLoader.active ? bubbleLoader.width : valueContainer.width)
text: outgoing ? qsTr("↑ Outgoing transaction") : qsTr("↓ Incoming transaction") + timeText.width + 3 * root.innerMargin
font.weight: Font.Medium height: childrenRect.height + root.innerMargin
anchors.top: parent.top radius: 16
anchors.topMargin: Style.current.halfPadding color: Style.current.background
anchors.horizontalCenter: parent.horizontalCenter border.color: Style.current.border
font.pixelSize: 13 border.width: 1
}
Item {
id: valueContainer
height: tokenText.height + fiatText.height
anchors.top: title.bottom
anchors.topMargin: 4
anchors.right: parent.right
anchors.rightMargin: 12
anchors.left: parent.left
anchors.leftMargin: 12
Image {
id: tokenImage
source: `../../../../img/tokens/${root.token}.png`
width: 24
height: 24
anchors.verticalCenter: parent.verticalCenter
}
StyledText { StyledText {
id: tokenText id: title
color: Style.current.text
text: `${root.tokenAmount} ${root.token}`
anchors.left: tokenImage.right
anchors.leftMargin: Style.current.halfPadding
font.pixelSize: 22
}
StyledText {
id: fiatText
color: Style.current.secondaryText color: Style.current.secondaryText
text: root.fiatValue text: outgoing ? qsTr("↑ Outgoing transaction") : qsTr("↓ Incoming transaction")
anchors.top: tokenText.bottom font.weight: Font.Medium
anchors.left: tokenText.left anchors.top: parent.top
anchors.topMargin: Style.current.halfPadding
anchors.left: parent.left
anchors.leftMargin: root.innerMargin
font.pixelSize: 13 font.pixelSize: 13
} }
}
Loader { Item {
id: bubbleLoader id: valueContainer
active: root.state !== Constants.addressRequested || !outgoing width: childrenRect.width
sourceComponent: stateBubbleComponent height: tokenText.height + fiatText.height
anchors.top: valueContainer.bottom anchors.top: title.bottom
anchors.topMargin: Style.current.halfPadding anchors.topMargin: 4
width: parent.width anchors.left: parent.left
height: item.height + 12 anchors.leftMargin: root.innerMargin
}
Component { Image {
id: stateBubbleComponent id: tokenImage
source: `../../../../img/tokens/${root.tokenSymbol}.png`
width: 24
height: 24
anchors.verticalCenter: parent.verticalCenter
}
StateBubble { StyledText {
state: root.state id: tokenText
color: Style.current.textColor
text: `${root.tokenAmount} ${root.tokenSymbol}`
anchors.left: tokenImage.right
anchors.leftMargin: Style.current.halfPadding
font.pixelSize: 22
}
StyledText {
id: fiatText
color: Style.current.secondaryText
text: root.fiatValue
anchors.top: tokenText.bottom
anchors.left: tokenText.left
font.pixelSize: 13
}
}
Loader {
id: bubbleLoader
active: isCurrentUser || (!isCurrentUser && !(root.state === Constants.addressRequested || root.state === Constants.transactionRequested))
sourceComponent: stateBubbleComponent
anchors.top: valueContainer.bottom
anchors.topMargin: Style.current.halfPadding
anchors.left: parent.left
anchors.leftMargin: root.innerMargin
}
Component {
id: stateBubbleComponent
StateBubble {
state: root.state
outgoing: root.outgoing
}
}
Loader {
id: buttonsLoader
active: (root.state === Constants.addressRequested && !root.outgoing) ||
(root.state === Constants.addressReceived && root.outgoing) ||
(root.state === Constants.transactionRequested && !root.outgoing)
sourceComponent: root.outgoing ? signAndSendComponent : acceptTransactionComponent
anchors.top: bubbleLoader.active ? bubbleLoader.bottom : valueContainer.bottom
anchors.topMargin: bubbleLoader.active ? root.innerMargin : 20
width: parent.width
}
Component {
id: acceptTransactionComponent
AcceptTransaction {
state: root.state
}
}
Component {
id: signAndSendComponent
SendTransactionButton {}
}
StyledText {
id: timeText
color: Style.current.secondaryText
text: Utils.formatTime(timestamp)
anchors.left: bubbleLoader.active ? bubbleLoader.right : undefined
anchors.leftMargin: bubbleLoader.active ? 13 : 0
anchors.right: bubbleLoader.active ? undefined : parent.right
anchors.rightMargin: bubbleLoader.active ? 0 : root.innerMargin
anchors.bottom: bubbleLoader.active ? bubbleLoader.bottom : buttonsLoader.top
anchors.bottomMargin: bubbleLoader.active ? -root.innerMargin : 7
font.pixelSize: 10
} }
} }
Loader {
id: buttonsLoader
active: (root.state === Constants.addressRequested && !root.outgoing) ||
(root.state === Constants.addressReceived && root.outgoing)
sourceComponent: root.outgoing ? signAndSendComponent : acceptTransactionComponent
anchors.top: bubbleLoader.active ? bubbleLoader.bottom : valueContainer.bottom
anchors.topMargin: bubbleLoader.active ? 0 : Style.current.halfPadding
width: parent.width
height: item.height
}
Component {
id: acceptTransactionComponent
AcceptTransaction {}
}
Component {
id: signAndSendComponent
SendTransactionButton {}
}
StyledText {
id: timeText
color: Style.current.secondaryText
text: Utils.formatTime(root.timestamp)
anchors.bottom: parent.bottom
anchors.bottomMargin: 9
anchors.right: parent.right
anchors.rightMargin: 12
font.pixelSize: 10
}
} }
/*##^## /*##^##

View File

@ -3,6 +3,8 @@ import "../../../../../../shared"
import "../../../../../../imports" import "../../../../../../imports"
Item { Item {
property int state: Constants.addressRequested
width: parent.width width: parent.width
height: childrenRect.height height: childrenRect.height
@ -13,14 +15,13 @@ Item {
StyledText { StyledText {
id: acceptText id: acceptText
color: Style.current.blue color: Style.current.blue
text: qsTr("Accept and share address") text: root.state === Constants.addressRequested ? qsTr("Accept and share address") : qsTr("Accept and send")
padding: Style.current.halfPadding
horizontalAlignment: Text.AlignHCenter horizontalAlignment: Text.AlignHCenter
wrapMode: Text.WordWrap wrapMode: Text.WordWrap
font.weight: Font.Medium font.weight: Font.Medium
anchors.right: parent.right anchors.right: parent.right
anchors.left: parent.left anchors.left: parent.left
bottomPadding: Style.current.halfPadding
topPadding: Style.current.halfPadding
anchors.top: separator1.bottom anchors.top: separator1.bottom
font.pixelSize: 15 font.pixelSize: 15
@ -28,7 +29,12 @@ Item {
anchors.fill: parent anchors.fill: parent
cursorShape: Qt.PointingHandCursor cursorShape: Qt.PointingHandCursor
onClicked: { onClicked: {
console.log('Accept') if (root.state === Constants.addressRequested) {
// TODO get address from a modal instead
chatsModel.acceptRequestAddressForTransaction(messageId, walletModel.getDefaultAccount())
} else if (root.state === Constants.transactionRequested) {
console.log('Accept and send')
}
} }
} }
} }
@ -48,8 +54,7 @@ Item {
font.weight: Font.Medium font.weight: Font.Medium
anchors.right: parent.right anchors.right: parent.right
anchors.left: parent.left anchors.left: parent.left
bottomPadding: Style.current.padding padding: Style.current.halfPadding
topPadding: Style.current.padding
anchors.top: separator2.bottom anchors.top: separator2.bottom
font.pixelSize: 15 font.pixelSize: 15
@ -57,7 +62,12 @@ Item {
anchors.fill: parent anchors.fill: parent
cursorShape: Qt.PointingHandCursor cursorShape: Qt.PointingHandCursor
onClicked: { onClicked: {
console.log('Decline') if (root.state === Constants.addressRequested) {
chatsModel.declineRequestAddressForTransaction(messageId)
} else if (root.state === Constants.transactionRequested) {
chatsModel.declineRequestTransaction(messageId)
}
} }
} }
} }

View File

@ -4,7 +4,7 @@ import "../../../../../../imports"
Item { Item {
width: parent.width width: parent.width
height: childrenRect.height height: childrenRect.height + Style.current.halfPadding
Separator { Separator {
id: separator id: separator
@ -19,9 +19,8 @@ Item {
font.weight: Font.Medium font.weight: Font.Medium
anchors.right: parent.right anchors.right: parent.right
anchors.left: parent.left anchors.left: parent.left
bottomPadding: Style.current.halfPadding
topPadding: Style.current.halfPadding topPadding: Style.current.halfPadding
anchors.top: separator1.bottom anchors.top: separator.bottom
font.pixelSize: 15 font.pixelSize: 15
MouseArea { MouseArea {

View File

@ -4,11 +4,12 @@ import "../../../../../../shared"
import "../../../../../../imports" import "../../../../../../imports"
Rectangle { Rectangle {
property string state: Constants.pending property int state: Constants.pending
property bool outgoing: true
id: root id: root
width: childrenRect.width + 12 width: childrenRect.width + 24
height: childrenRect.height height: 28
border.width: 1 border.width: 1
border.color: Style.current.border border.color: Style.current.border
radius: 24 radius: 24
@ -19,11 +20,10 @@ Rectangle {
switch (root.state) { switch (root.state) {
case Constants.pending: case Constants.pending:
case Constants.addressReceived: case Constants.addressReceived:
case Constants.shared: case Constants.transactionRequested:
case Constants.addressRequested: return "../../../../../img/dotsLoadings.svg" case Constants.addressRequested: return "../../../../../img/dotsLoadings.svg"
case Constants.confirmed: return "../../../../../img/check.svg" case Constants.confirmed: return "../../../../../img/check.svg"
case Constants.unknown: case Constants.transactionDeclined:
case Constants.failure:
case Constants.declined: return "../../../../../img/exclamation.svg" case Constants.declined: return "../../../../../img/exclamation.svg"
default: return "" default: return ""
} }
@ -38,7 +38,7 @@ Rectangle {
ColorOverlay { ColorOverlay {
anchors.fill: stateImage anchors.fill: stateImage
source: stateImage source: stateImage
color: state == Constants.confirmed ? Style.current.transparent : Style.current.text color: state === Constants.confirmed ? Style.current.transparent : Style.current.textColor
} }
StyledText { StyledText {
@ -48,7 +48,7 @@ Rectangle {
return Style.current.danger return Style.current.danger
} }
if (root.state === Constants.confirmed || root.state === Constants.declined) { if (root.state === Constants.confirmed || root.state === Constants.declined) {
return Style.current.text return Style.current.textColor
} }
return Style.current.secondaryText return Style.current.secondaryText
@ -59,9 +59,10 @@ Rectangle {
case Constants.confirmed: return qsTr("Confirmed") case Constants.confirmed: return qsTr("Confirmed")
case Constants.unknown: return qsTr("Unknown token") case Constants.unknown: return qsTr("Unknown token")
case Constants.addressRequested: return qsTr("Address requested") case Constants.addressRequested: return qsTr("Address requested")
case Constants.addressReceived: return qsTr("Address received") case Constants.transactionRequested: return qsTr("Waiting to accept")
case Constants.addressReceived: return (!root.outgoing ? qsTr("Address shared") : qsTr("Address received"))
case Constants.transactionDeclined:
case Constants.declined: return qsTr("Transaction declined") case Constants.declined: return qsTr("Transaction declined")
case Constants.shared: return qsTr("Shared Other Account")
case Constants.failure: return qsTr("Failure") case Constants.failure: return qsTr("Failure")
default: return qsTr("Unknown state") default: return qsTr("Unknown state")
} }
@ -69,8 +70,7 @@ Rectangle {
font.weight: Font.Medium font.weight: Font.Medium
anchors.left: stateImage.right anchors.left: stateImage.right
anchors.leftMargin: 4 anchors.leftMargin: 4
bottomPadding: Style.current.halfPadding anchors.verticalCenter: parent.verticalCenter
topPadding: Style.current.halfPadding
font.pixelSize: 13 font.pixelSize: 13
} }
} }

View File

@ -14,6 +14,12 @@ ModalPopup {
title: qsTrId("command-button-send") title: qsTrId("command-button-send")
height: 504 height: 504
property var selectedRecipient
onSelectedRecipientChanged: {
selectRecipient.selectedRecipient = this.selectedRecipient
selectRecipient.readOnly = !!this.selectedRecipient && !!this.selectedRecipient.address
}
property MessageDialog sendingError: MessageDialog { property MessageDialog sendingError: MessageDialog {
id: sendingError id: sendingError
title: qsTr("Error sending the transaction") title: qsTr("Error sending the transaction")
@ -94,6 +100,8 @@ ModalPopup {
accounts: walletModel.accounts accounts: walletModel.accounts
contacts: profileModel.addedContacts contacts: profileModel.addedContacts
label: qsTr("Recipient") label: qsTr("Recipient")
readOnly: !!root.selectedRecipient && !!root.selectedRecipient.address
selectedRecipient: root.selectedRecipient
anchors.top: separator.bottom anchors.top: separator.bottom
anchors.topMargin: 10 anchors.topMargin: 10
width: stack.width width: stack.width

View File

@ -0,0 +1,24 @@
This is free and unencumbered software released into the public domain.
Anyone is free to copy, modify, publish, use, compile, sell, or
distribute this software, either in source code form or as a compiled
binary, for any purpose, commercial or non-commercial, and by any
means.
In jurisdictions that recognize copyright laws, the author or authors
of this software dedicate any and all copyright interest in the
software to the public domain. We make this dedication for the benefit
of the public at large and to the detriment of our heirs and
successors. We intend this dedication to be an overt act of
relinquishment in perpetuity of all present and future rights to this
software under copyright law.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
IN NO EVENT SHALL THE AUTHORS BE LIABLE FOR ANY CLAIM, DAMAGES OR
OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE,
ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR
OTHER DEALINGS IN THE SOFTWARE.
For more information, please refer to <http://unlicense.org>

File diff suppressed because it is too large Load Diff

View File

@ -25,14 +25,13 @@ QtObject {
readonly property string generatedWalletType: "generated" readonly property string generatedWalletType: "generated"
// Transaction states // Transaction states
readonly property string pending: "pending" readonly property int addressRequested: 1
readonly property string confirmed: "confirmed" readonly property int declined: 2
readonly property string unknown: "unknown" readonly property int addressReceived: 3
readonly property string addressRequested: "addressRequested" readonly property int transactionRequested: 4
readonly property string addressReceived: "addressReceived" readonly property int transactionDeclined: 5
readonly property string declined: "declined" readonly property int pending: 6
readonly property string shared: "shared" readonly property int confirmed: 7
readonly property string failure: "failure"
readonly property var accountColors: [ readonly property var accountColors: [
"#9B832F", "#9B832F",

View File

@ -2,8 +2,14 @@ pragma Singleton
import QtQuick 2.13 import QtQuick 2.13
import "../shared/xss.js" as XSS import "../shared/xss.js" as XSS
import "./BigNumber/bignumber.js" as BigNumber
QtObject { QtObject {
function newBigNumber(number) {
// See here for docs: https://github.com/peterolson/BigInteger.js
return BigNumber.bigInt(number)
}
function isHex(value) { function isHex(value) {
return /^(-0x|0x)?[0-9a-fA-F]*$/i.test(value) return /^(-0x|0x)?[0-9a-fA-F]*$/i.test(value)
} }

View File

@ -167,6 +167,7 @@ DISTFILES += \
app/AppLayouts/Wallet/components/collectiblesComponents/CollectiblesModal.qml \ app/AppLayouts/Wallet/components/collectiblesComponents/CollectiblesModal.qml \
app/AppLayouts/Wallet/components/collectiblesComponents/CollectiblesModalContent.qml \ app/AppLayouts/Wallet/components/collectiblesComponents/CollectiblesModalContent.qml \
app/AppLayouts/Wallet/components/collectiblesComponents/collectiblesData.js \ app/AppLayouts/Wallet/components/collectiblesComponents/collectiblesData.js \
app/AppLayouts/Wallet/data/Tokens.qml \
fonts/InterStatus/InterStatus-Black.otf \ fonts/InterStatus/InterStatus-Black.otf \
fonts/InterStatus/InterStatus-BlackItalic.otf \ fonts/InterStatus/InterStatus-BlackItalic.otf \
fonts/InterStatus/InterStatus-Bold.otf \ fonts/InterStatus/InterStatus-Bold.otf \

View File

@ -18,6 +18,7 @@ Item {
property int iconHeight: 24 property int iconHeight: 24
property int iconWidth: 24 property int iconWidth: 24
property bool copyToClipboard: false property bool copyToClipboard: false
property bool readOnly: false
readonly property bool hasIcon: icon.toString() !== "" readonly property bool hasIcon: icon.toString() !== ""
readonly property var forceActiveFocus: function () { readonly property var forceActiveFocus: function () {
@ -65,7 +66,7 @@ Item {
if (!!validationError) { if (!!validationError) {
return Style.current.danger return Style.current.danger
} }
if (inputValue.focus) { if (!inputBox.readOnly && inputValue.focus) {
return Style.current.inputBorderFocus return Style.current.inputBorderFocus
} }
return Style.current.transparent return Style.current.transparent
@ -87,6 +88,7 @@ Item {
leftPadding: inputBox.hasIcon ? iconWidth + 20 : Style.current.padding leftPadding: inputBox.hasIcon ? iconWidth + 20 : Style.current.padding
selectByMouse: true selectByMouse: true
font.pixelSize: fontPixelSize font.pixelSize: fontPixelSize
readOnly: inputBox.readOnly
background: Rectangle { background: Rectangle {
color: Style.current.transparent color: Style.current.transparent
} }

View File

@ -13,7 +13,7 @@ Item {
property alias label: txtLabel.text property alias label: txtLabel.text
// If supplied, additional info will be displayed top-right in danger colour (red) // If supplied, additional info will be displayed top-right in danger colour (red)
property alias additionalInfo: txtAddlInfo.text property alias additionalInfo: txtAddlInfo.text
property var selectedRecipient: { } property var selectedRecipient
property bool readOnly: false property bool readOnly: false
height: (readOnly ? inpReadOnly.height : inpAddress.height) + txtLabel.height height: (readOnly ? inpReadOnly.height : inpAddress.height) + txtLabel.height
//% "Invalid ethereum address" //% "Invalid ethereum address"
@ -106,7 +106,7 @@ Item {
textField.verticalAlignment: TextField.AlignVCenter textField.verticalAlignment: TextField.AlignVCenter
textField.font.pixelSize: 15 textField.font.pixelSize: 15
textField.color: Style.current.secondaryText textField.color: Style.current.secondaryText
textField.readOnly: true readOnly: true
validationErrorAlignment: TextEdit.AlignRight validationErrorAlignment: TextEdit.AlignRight
validationErrorTopMargin: 8 validationErrorTopMargin: 8
customHeight: 56 customHeight: 56