diff --git a/src/app/chat/event_handling.nim b/src/app/chat/event_handling.nim index b636367129..6d7a3b95c5 100644 --- a/src/app/chat/event_handling.nim +++ b/src/app/chat/event_handling.nim @@ -24,6 +24,10 @@ proc handleChatEvents(self: ChatController) = var evArgs = ChatUpdateArgs(e) 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): var args = ChannelArgs(e) self.view.clearMessages(args.chat.id) diff --git a/src/app/chat/view.nim b/src/app/chat/view.nim index 0d74aa2e1a..d2f799cb66 100644 --- a/src/app/chat/view.nim +++ b/src/app/chat/view.nim @@ -488,6 +488,9 @@ QtObject: self.currentSuggestions.setNewData(self.status.contacts.getContacts()) self.calculateUnreadMessages() + proc deleteMessage*(self: ChatsView, channelId: string, messageId: string) = + self.messageList[channelId].deleteMessage(messageId) + proc renameGroup*(self: ChatsView, newName: string) {.slot.} = self.status.chat.renameGroup(self.activeChannel.id, newName) @@ -570,5 +573,15 @@ QtObject: if (self.chats.chats.len == 0): return false let selectedChannel = self.chats.getChannel(channelIndex) 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) diff --git a/src/app/chat/views/message_list.nim b/src/app/chat/views/message_list.nim index 59ad000ed9..e41b79998e 100644 --- a/src/app/chat/views/message_list.nim +++ b/src/app/chat/views/message_list.nim @@ -1,4 +1,4 @@ -import NimQml, Tables, sets +import NimQml, Tables, sets, json import ../../../status/status import ../../../status/accounts import ../../../status/chat @@ -31,6 +31,7 @@ type Audio = UserRole + 20 AudioDurationMs = UserRole + 21 EmojiReactions = UserRole + 22 + CommandParameters = UserRole + 23 QtObject: type @@ -69,6 +70,14 @@ QtObject: result.status = status 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) = if not self.messageIndex.hasKey(messageId): return let msgIdx = self.messageIndex[messageId] @@ -126,6 +135,17 @@ QtObject: of ChatMessageRoles.Audio: result = newQVariant(message.audio) of ChatMessageRoles.AudioDurationMs: result = newQVariant(message.audioDurationMs) 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] = { @@ -150,7 +170,8 @@ QtObject: ChatMessageRoles.Image.int: "image", ChatMessageRoles.Audio.int: "audio", ChatMessageRoles.AudioDurationMs.int: "audioDurationMs", - ChatMessageRoles.EmojiReactions.int: "emojiReactions" + ChatMessageRoles.EmojiReactions.int: "emojiReactions", + ChatMessageRoles.CommandParameters.int: "commandParameters" }.toTable proc getMessageIndex(self: ChatMessageList, messageId: string): int {.slot.} = diff --git a/src/status/chat.nim b/src/status/chat.nim index a120262a36..2657e7f64e 100644 --- a/src/status/chat.nim +++ b/src/status/chat.nim @@ -2,6 +2,7 @@ import eventemitter, json, strutils, sequtils, tables, chronicles, sugar, times import libstatus/contracts as status_contracts 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/types import mailservers @@ -265,19 +266,20 @@ proc clearHistory*(self: ChatModel, chatId: string) = proc setActiveChannel*(self: ChatModel, chatId: string) = self.events.emit("activeChannelChanged", ChatIdArg(chatId: 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) - var (chats, messages) = self.processChatUpdate(parseJson(response)) +proc processMessageUpdateAfterSend(self: ChatModel, response: string): (seq[Chat], seq[Message]) = + result = self.processChatUpdate(parseJson(response)) + var (chats, messages) = result 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 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) = var response = status_chat.sendImageMessage(chatId, image) - var (chats, messages) = self.processChatUpdate(parseJson(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)) + discard self.processMessageUpdateAfterSend(response) proc addStickerToRecent*(self: ChatModel, sticker: Sticker, save: bool = false) = self.recentStickers.insert(sticker, 0) @@ -396,3 +398,20 @@ proc muteChat*(self: ChatModel, chatId: string) = proc unmuteChat*(self: ChatModel, chatId: string) = 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) diff --git a/src/status/chat/message.nim b/src/status/chat/message.nim index a2335245bf..7870d72c4f 100644 --- a/src/status/chat/message.nim +++ b/src/status/chat/message.nim @@ -19,11 +19,21 @@ type TextItem* = object literal*: 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 alias*: string chatId*: string clock*: int - # commandParameters*: # ??? + commandParameters*: CommandParameters contentType*: ContentType ensName*: string fromAuthor*: string @@ -32,7 +42,7 @@ type Message* = object lineCount*: int localChatId*: string messageType*: string # ??? - parsedText*: seq[TextItem] + parsedText*: seq[TextItem] # quotedMessage: # ??? replace*: string # ??? responseTo*: string diff --git a/src/status/libstatus/chat.nim b/src/status/libstatus/chat.nim index 6e11c97855..1dbf0d1ffe 100644 --- a/src/status/libstatus/chat.nim +++ b/src/status/libstatus/chat.nim @@ -58,7 +58,6 @@ proc chatMessages*(chatId: string, cursor: string = ""): (string, seq[Message]) cursorVal = newJString(cursor) let rpcResult = parseJson(callPrivateRPC("chatMessages".prefix, %* [chatId, cursorVal, 20]))["result"] - if rpcResult["messages"].kind != JNull: for jsonMsg in rpcResult["messages"]: messages.add(jsonMsg.toMessage) diff --git a/src/status/libstatus/chatCommands.nim b/src/status/libstatus/chatCommands.nim new file mode 100644 index 0000000000..42ba685f74 --- /dev/null +++ b/src/status/libstatus/chatCommands.nim @@ -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]) diff --git a/src/status/signals/messages.nim b/src/status/signals/messages.nim index d65a4809dc..b8c41c40e1 100644 --- a/src/status/signals/messages.nim +++ b/src/status/signals/messages.nim @@ -198,6 +198,19 @@ proc toMessage*(jsonMsg: JsonNode): Message = if message.contentType == ContentType.Sticker: 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 diff --git a/src/status/wallet.nim b/src/status/wallet.nim index 383e433d8d..cbad8efd82 100644 --- a/src/status/wallet.nim +++ b/src/status/wallet.nim @@ -136,7 +136,6 @@ proc updateAccount*(self: WalletModel, address: string) = self.events.emit("accountsUpdated", Args()) proc getTotalFiatBalance*(self: WalletModel): string = - var newBalance = 0.0 fmt"{self.totalBalance:.2f} {self.defaultCurrency}" proc convertValue*(self: WalletModel, balance: string, fromCurrency: string, toCurrency: string): float = diff --git a/ui/app/AppLayouts/Chat/ChatColumn/ChatComponents/ChatCommandButton.qml b/ui/app/AppLayouts/Chat/ChatColumn/ChatComponents/ChatCommandButton.qml index 8279e8d859..f062c078bc 100644 --- a/ui/app/AppLayouts/Chat/ChatColumn/ChatComponents/ChatCommandButton.qml +++ b/ui/app/AppLayouts/Chat/ChatColumn/ChatComponents/ChatCommandButton.qml @@ -61,6 +61,8 @@ Rectangle { MouseArea { anchors.fill: parent cursorShape: Qt.PointingHandCursor - onClicked: root.onClicked + onClicked: { + root.onClicked() + } } } diff --git a/ui/app/AppLayouts/Chat/ChatColumn/ChatComponents/ChatCommandsPopup.qml b/ui/app/AppLayouts/Chat/ChatColumn/ChatComponents/ChatCommandsPopup.qml index da31ef5955..28c7d721c0 100644 --- a/ui/app/AppLayouts/Chat/ChatColumn/ChatComponents/ChatCommandsPopup.qml +++ b/ui/app/AppLayouts/Chat/ChatColumn/ChatComponents/ChatCommandsPopup.qml @@ -3,51 +3,76 @@ import QtQuick.Controls 2.13 import QtGraphicalEffects 1.13 import "../../../../../imports" import "../../../../../shared" +import "../../../Wallet" Popup { - id: root - width: buttonRow.width - height: buttonRow.height - padding: 0 - margins: 0 + id: root + width: buttonRow.width + height: buttonRow.height + padding: 0 + margins: 0 - background: Rectangle { - color: Style.current.background - radius: Style.current.radius - border.width: 0 - layer.enabled: true - layer.effect: DropShadow{ - verticalOffset: 3 - radius: 8 - samples: 15 - fast: true - cached: true - color: "#22000000" - } - } + background: Rectangle { + color: Style.current.background + radius: Style.current.radius + border.width: 0 + layer.enabled: true + layer.effect: DropShadow { + verticalOffset: 3 + radius: 8 + samples: 15 + fast: true + cached: true + color: "#22000000" + } + } - Row { - id: buttonRow - anchors.left: parent.left - anchors.leftMargin: 0 - anchors.top: parent.top - anchors.topMargin: 0 - padding: Style.current.halfPadding - spacing: Style.current.halfPadding + Row { + id: buttonRow + anchors.left: parent.left + anchors.leftMargin: 0 + anchors.top: parent.top + anchors.topMargin: 0 + padding: Style.current.halfPadding + spacing: Style.current.halfPadding - ChatCommandButton { - iconColor: Style.current.purple - iconSource: "../../../../img/send.svg" - //% "Send transaction" - text: qsTrId("send-transaction") - } + ChatCommandButton { + iconColor: Style.current.purple + iconSource: "../../../../img/send.svg" + //% "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 { - iconColor: Style.current.orange - iconSource: "../../../../img/send.svg" - rotatedImage: true - //% "Request transaction" - text: qsTrId("request-transaction") - } - } + ChatCommandButton { + iconColor: Style.current.orange + iconSource: "../../../../img/send.svg" + rotatedImage: true + //% "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 + } + } + } + } } diff --git a/ui/app/AppLayouts/Chat/ChatColumn/Message.qml b/ui/app/AppLayouts/Chat/ChatColumn/Message.qml index 59db4ad2eb..2c3aee0d17 100644 --- a/ui/app/AppLayouts/Chat/ChatColumn/Message.qml +++ b/ui/app/AppLayouts/Chat/ChatColumn/Message.qml @@ -79,6 +79,8 @@ Item { return fetchMoreMessagesButtonComponent case Constants.systemMessagePrivateGroupType: return privateGroupHeaderComponent + case Constants.transactionType: + return transactionBubble default: return appSettings.compactMode ? compactMessageComponent : messageComponent } @@ -174,6 +176,12 @@ Item { clickMessage: messageItem.clickMessage } } + + // Transaction bubble + Component { + id: transactionBubble + TransactionBubble {} + } } /*##^## diff --git a/ui/app/AppLayouts/Chat/ChatColumn/MessageComponents/TransactionBubble.qml b/ui/app/AppLayouts/Chat/ChatColumn/MessageComponents/TransactionBubble.qml index e698e25672..ce169bbe1a 100644 --- a/ui/app/AppLayouts/Chat/ChatColumn/MessageComponents/TransactionBubble.qml +++ b/ui/app/AppLayouts/Chat/ChatColumn/MessageComponents/TransactionBubble.qml @@ -2,123 +2,214 @@ import QtQuick 2.3 import "../../../../../shared" import "../../../../../imports" import "./TransactionComponents" +import "../../../Wallet/data" -Rectangle { - property string tokenAmount: "100" - property string token: "SNT" - property string fiatValue: "10 USD" - property bool outgoing: true - property string state: "addressReceived" - property int timestamp: 1598454756329 +Item { + property var commandParametersObject: { + try { + var result = JSON.parse(commandParameters) + + return result + } 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 - width: 170 - height: childrenRect.height - radius: 16 - color: Style.current.background - border.color: Style.current.border - border.width: 1 + anchors.left: parent.left + anchors.leftMargin: appSettings.compactMode ? Style.current.padding : 48 + width: rectangleBubble.width + height: rectangleBubble.height - StyledText { - id: title - color: Style.current.secondaryText - text: outgoing ? qsTr("↑ Outgoing transaction") : qsTr("↓ Incoming transaction") - font.weight: Font.Medium - anchors.top: parent.top - anchors.topMargin: Style.current.halfPadding - anchors.horizontalCenter: parent.horizontalCenter - font.pixelSize: 13 - } - - 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 - } + Rectangle { + id: rectangleBubble + width: (bubbleLoader.active ? bubbleLoader.width : valueContainer.width) + + timeText.width + 3 * root.innerMargin + height: childrenRect.height + root.innerMargin + radius: 16 + color: Style.current.background + border.color: Style.current.border + border.width: 1 StyledText { - id: tokenText - color: Style.current.text - text: `${root.tokenAmount} ${root.token}` - anchors.left: tokenImage.right - anchors.leftMargin: Style.current.halfPadding - font.pixelSize: 22 - } - - StyledText { - id: fiatText + id: title color: Style.current.secondaryText - text: root.fiatValue - anchors.top: tokenText.bottom - anchors.left: tokenText.left + text: outgoing ? qsTr("↑ Outgoing transaction") : qsTr("↓ Incoming transaction") + font.weight: Font.Medium + anchors.top: parent.top + anchors.topMargin: Style.current.halfPadding + anchors.left: parent.left + anchors.leftMargin: root.innerMargin font.pixelSize: 13 } - } - Loader { - id: bubbleLoader - active: root.state !== Constants.addressRequested || !outgoing - sourceComponent: stateBubbleComponent - anchors.top: valueContainer.bottom - anchors.topMargin: Style.current.halfPadding - width: parent.width - height: item.height + 12 - } + Item { + id: valueContainer + width: childrenRect.width + height: tokenText.height + fiatText.height + anchors.top: title.bottom + anchors.topMargin: 4 + anchors.left: parent.left + anchors.leftMargin: root.innerMargin - Component { - id: stateBubbleComponent + Image { + id: tokenImage + source: `../../../../img/tokens/${root.tokenSymbol}.png` + width: 24 + height: 24 + anchors.verticalCenter: parent.verticalCenter + } - StateBubble { - state: root.state + StyledText { + 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 - } - } /*##^## diff --git a/ui/app/AppLayouts/Chat/ChatColumn/MessageComponents/TransactionComponents/AcceptTransaction.qml b/ui/app/AppLayouts/Chat/ChatColumn/MessageComponents/TransactionComponents/AcceptTransaction.qml index 319e898803..01a350d130 100644 --- a/ui/app/AppLayouts/Chat/ChatColumn/MessageComponents/TransactionComponents/AcceptTransaction.qml +++ b/ui/app/AppLayouts/Chat/ChatColumn/MessageComponents/TransactionComponents/AcceptTransaction.qml @@ -3,6 +3,8 @@ import "../../../../../../shared" import "../../../../../../imports" Item { + property int state: Constants.addressRequested + width: parent.width height: childrenRect.height @@ -13,14 +15,13 @@ Item { StyledText { id: acceptText 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 wrapMode: Text.WordWrap font.weight: Font.Medium anchors.right: parent.right anchors.left: parent.left - bottomPadding: Style.current.halfPadding - topPadding: Style.current.halfPadding anchors.top: separator1.bottom font.pixelSize: 15 @@ -28,7 +29,12 @@ Item { anchors.fill: parent cursorShape: Qt.PointingHandCursor 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 anchors.right: parent.right anchors.left: parent.left - bottomPadding: Style.current.padding - topPadding: Style.current.padding + padding: Style.current.halfPadding anchors.top: separator2.bottom font.pixelSize: 15 @@ -57,7 +62,12 @@ Item { anchors.fill: parent cursorShape: Qt.PointingHandCursor onClicked: { - console.log('Decline') + if (root.state === Constants.addressRequested) { + chatsModel.declineRequestAddressForTransaction(messageId) + } else if (root.state === Constants.transactionRequested) { + chatsModel.declineRequestTransaction(messageId) + } + } } } diff --git a/ui/app/AppLayouts/Chat/ChatColumn/MessageComponents/TransactionComponents/SendTransactionButton.qml b/ui/app/AppLayouts/Chat/ChatColumn/MessageComponents/TransactionComponents/SendTransactionButton.qml index 9fc39f8f32..04e0710b27 100644 --- a/ui/app/AppLayouts/Chat/ChatColumn/MessageComponents/TransactionComponents/SendTransactionButton.qml +++ b/ui/app/AppLayouts/Chat/ChatColumn/MessageComponents/TransactionComponents/SendTransactionButton.qml @@ -4,7 +4,7 @@ import "../../../../../../imports" Item { width: parent.width - height: childrenRect.height + height: childrenRect.height + Style.current.halfPadding Separator { id: separator @@ -19,9 +19,8 @@ Item { font.weight: Font.Medium anchors.right: parent.right anchors.left: parent.left - bottomPadding: Style.current.halfPadding topPadding: Style.current.halfPadding - anchors.top: separator1.bottom + anchors.top: separator.bottom font.pixelSize: 15 MouseArea { diff --git a/ui/app/AppLayouts/Chat/ChatColumn/MessageComponents/TransactionComponents/StateBubble.qml b/ui/app/AppLayouts/Chat/ChatColumn/MessageComponents/TransactionComponents/StateBubble.qml index e0ce7e2737..c7c5bdbd8b 100644 --- a/ui/app/AppLayouts/Chat/ChatColumn/MessageComponents/TransactionComponents/StateBubble.qml +++ b/ui/app/AppLayouts/Chat/ChatColumn/MessageComponents/TransactionComponents/StateBubble.qml @@ -4,11 +4,12 @@ import "../../../../../../shared" import "../../../../../../imports" Rectangle { - property string state: Constants.pending + property int state: Constants.pending + property bool outgoing: true id: root - width: childrenRect.width + 12 - height: childrenRect.height + width: childrenRect.width + 24 + height: 28 border.width: 1 border.color: Style.current.border radius: 24 @@ -19,11 +20,10 @@ Rectangle { switch (root.state) { case Constants.pending: case Constants.addressReceived: - case Constants.shared: + case Constants.transactionRequested: case Constants.addressRequested: return "../../../../../img/dotsLoadings.svg" case Constants.confirmed: return "../../../../../img/check.svg" - case Constants.unknown: - case Constants.failure: + case Constants.transactionDeclined: case Constants.declined: return "../../../../../img/exclamation.svg" default: return "" } @@ -38,7 +38,7 @@ Rectangle { ColorOverlay { anchors.fill: stateImage source: stateImage - color: state == Constants.confirmed ? Style.current.transparent : Style.current.text + color: state === Constants.confirmed ? Style.current.transparent : Style.current.textColor } StyledText { @@ -48,7 +48,7 @@ Rectangle { return Style.current.danger } if (root.state === Constants.confirmed || root.state === Constants.declined) { - return Style.current.text + return Style.current.textColor } return Style.current.secondaryText @@ -59,9 +59,10 @@ Rectangle { case Constants.confirmed: return qsTr("Confirmed") case Constants.unknown: return qsTr("Unknown token") 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.shared: return qsTr("Shared ‘Other Account’") case Constants.failure: return qsTr("Failure") default: return qsTr("Unknown state") } @@ -69,8 +70,7 @@ Rectangle { font.weight: Font.Medium anchors.left: stateImage.right anchors.leftMargin: 4 - bottomPadding: Style.current.halfPadding - topPadding: Style.current.halfPadding + anchors.verticalCenter: parent.verticalCenter font.pixelSize: 13 } } diff --git a/ui/app/AppLayouts/Wallet/SendModal.qml b/ui/app/AppLayouts/Wallet/SendModal.qml index cde4c8ad6a..da6defb680 100644 --- a/ui/app/AppLayouts/Wallet/SendModal.qml +++ b/ui/app/AppLayouts/Wallet/SendModal.qml @@ -14,6 +14,12 @@ ModalPopup { title: qsTrId("command-button-send") height: 504 + property var selectedRecipient + onSelectedRecipientChanged: { + selectRecipient.selectedRecipient = this.selectedRecipient + selectRecipient.readOnly = !!this.selectedRecipient && !!this.selectedRecipient.address + } + property MessageDialog sendingError: MessageDialog { id: sendingError title: qsTr("Error sending the transaction") @@ -94,6 +100,8 @@ ModalPopup { accounts: walletModel.accounts contacts: profileModel.addedContacts label: qsTr("Recipient") + readOnly: !!root.selectedRecipient && !!root.selectedRecipient.address + selectedRecipient: root.selectedRecipient anchors.top: separator.bottom anchors.topMargin: 10 width: stack.width diff --git a/ui/imports/BigNumber/LICENSE b/ui/imports/BigNumber/LICENSE new file mode 100644 index 0000000000..cf1ab25da0 --- /dev/null +++ b/ui/imports/BigNumber/LICENSE @@ -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 diff --git a/ui/imports/BigNumber/bignumber.js b/ui/imports/BigNumber/bignumber.js new file mode 100644 index 0000000000..f7fa904520 --- /dev/null +++ b/ui/imports/BigNumber/bignumber.js @@ -0,0 +1,1453 @@ +var bigInt = (function (undefined) { + "use strict"; + + var BASE = 1e7, + LOG_BASE = 7, + MAX_INT = 9007199254740992, + MAX_INT_ARR = smallToArray(MAX_INT), + DEFAULT_ALPHABET = "0123456789abcdefghijklmnopqrstuvwxyz"; + + var supportsNativeBigInt = typeof BigInt === "function"; + + function Integer(v, radix, alphabet, caseSensitive) { + if (typeof v === "undefined") return Integer[0]; + if (typeof radix !== "undefined") return +radix === 10 && !alphabet ? parseValue(v) : parseBase(v, radix, alphabet, caseSensitive); + return parseValue(v); + } + + function BigInteger(value, sign) { + this.value = value; + this.sign = sign; + this.isSmall = false; + } + BigInteger.prototype = Object.create(Integer.prototype); + + function SmallInteger(value) { + this.value = value; + this.sign = value < 0; + this.isSmall = true; + } + SmallInteger.prototype = Object.create(Integer.prototype); + + function NativeBigInt(value) { + this.value = value; + } + NativeBigInt.prototype = Object.create(Integer.prototype); + + function isPrecise(n) { + return -MAX_INT < n && n < MAX_INT; + } + + function smallToArray(n) { // For performance reasons doesn't reference BASE, need to change this function if BASE changes + if (n < 1e7) + return [n]; + if (n < 1e14) + return [n % 1e7, Math.floor(n / 1e7)]; + return [n % 1e7, Math.floor(n / 1e7) % 1e7, Math.floor(n / 1e14)]; + } + + function arrayToSmall(arr) { // If BASE changes this function may need to change + trim(arr); + var length = arr.length; + if (length < 4 && compareAbs(arr, MAX_INT_ARR) < 0) { + switch (length) { + case 0: return 0; + case 1: return arr[0]; + case 2: return arr[0] + arr[1] * BASE; + default: return arr[0] + (arr[1] + arr[2] * BASE) * BASE; + } + } + return arr; + } + + function trim(v) { + var i = v.length; + while (v[--i] === 0); + v.length = i + 1; + } + + function createArray(length) { // function shamelessly stolen from Yaffle's library https://github.com/Yaffle/BigInteger + var x = new Array(length); + var i = -1; + while (++i < length) { + x[i] = 0; + } + return x; + } + + function truncate(n) { + if (n > 0) return Math.floor(n); + return Math.ceil(n); + } + + function add(a, b) { // assumes a and b are arrays with a.length >= b.length + var l_a = a.length, + l_b = b.length, + r = new Array(l_a), + carry = 0, + base = BASE, + sum, i; + for (i = 0; i < l_b; i++) { + sum = a[i] + b[i] + carry; + carry = sum >= base ? 1 : 0; + r[i] = sum - carry * base; + } + while (i < l_a) { + sum = a[i] + carry; + carry = sum === base ? 1 : 0; + r[i++] = sum - carry * base; + } + if (carry > 0) r.push(carry); + return r; + } + + function addAny(a, b) { + if (a.length >= b.length) return add(a, b); + return add(b, a); + } + + function addSmall(a, carry) { // assumes a is array, carry is number with 0 <= carry < MAX_INT + var l = a.length, + r = new Array(l), + base = BASE, + sum, i; + for (i = 0; i < l; i++) { + sum = a[i] - base + carry; + carry = Math.floor(sum / base); + r[i] = sum - carry * base; + carry += 1; + } + while (carry > 0) { + r[i++] = carry % base; + carry = Math.floor(carry / base); + } + return r; + } + + BigInteger.prototype.add = function (v) { + var n = parseValue(v); + if (this.sign !== n.sign) { + return this.subtract(n.negate()); + } + var a = this.value, b = n.value; + if (n.isSmall) { + return new BigInteger(addSmall(a, Math.abs(b)), this.sign); + } + return new BigInteger(addAny(a, b), this.sign); + }; + BigInteger.prototype.plus = BigInteger.prototype.add; + + SmallInteger.prototype.add = function (v) { + var n = parseValue(v); + var a = this.value; + if (a < 0 !== n.sign) { + return this.subtract(n.negate()); + } + var b = n.value; + if (n.isSmall) { + if (isPrecise(a + b)) return new SmallInteger(a + b); + b = smallToArray(Math.abs(b)); + } + return new BigInteger(addSmall(b, Math.abs(a)), a < 0); + }; + SmallInteger.prototype.plus = SmallInteger.prototype.add; + + NativeBigInt.prototype.add = function (v) { + return new NativeBigInt(this.value + parseValue(v).value); + } + NativeBigInt.prototype.plus = NativeBigInt.prototype.add; + + function subtract(a, b) { // assumes a and b are arrays with a >= b + var a_l = a.length, + b_l = b.length, + r = new Array(a_l), + borrow = 0, + base = BASE, + i, difference; + for (i = 0; i < b_l; i++) { + difference = a[i] - borrow - b[i]; + if (difference < 0) { + difference += base; + borrow = 1; + } else borrow = 0; + r[i] = difference; + } + for (i = b_l; i < a_l; i++) { + difference = a[i] - borrow; + if (difference < 0) difference += base; + else { + r[i++] = difference; + break; + } + r[i] = difference; + } + for (; i < a_l; i++) { + r[i] = a[i]; + } + trim(r); + return r; + } + + function subtractAny(a, b, sign) { + var value; + if (compareAbs(a, b) >= 0) { + value = subtract(a, b); + } else { + value = subtract(b, a); + sign = !sign; + } + value = arrayToSmall(value); + if (typeof value === "number") { + if (sign) value = -value; + return new SmallInteger(value); + } + return new BigInteger(value, sign); + } + + function subtractSmall(a, b, sign) { // assumes a is array, b is number with 0 <= b < MAX_INT + var l = a.length, + r = new Array(l), + carry = -b, + base = BASE, + i, difference; + for (i = 0; i < l; i++) { + difference = a[i] + carry; + carry = Math.floor(difference / base); + difference %= base; + r[i] = difference < 0 ? difference + base : difference; + } + r = arrayToSmall(r); + if (typeof r === "number") { + if (sign) r = -r; + return new SmallInteger(r); + } return new BigInteger(r, sign); + } + + BigInteger.prototype.subtract = function (v) { + var n = parseValue(v); + if (this.sign !== n.sign) { + return this.add(n.negate()); + } + var a = this.value, b = n.value; + if (n.isSmall) + return subtractSmall(a, Math.abs(b), this.sign); + return subtractAny(a, b, this.sign); + }; + BigInteger.prototype.minus = BigInteger.prototype.subtract; + + SmallInteger.prototype.subtract = function (v) { + var n = parseValue(v); + var a = this.value; + if (a < 0 !== n.sign) { + return this.add(n.negate()); + } + var b = n.value; + if (n.isSmall) { + return new SmallInteger(a - b); + } + return subtractSmall(b, Math.abs(a), a >= 0); + }; + SmallInteger.prototype.minus = SmallInteger.prototype.subtract; + + NativeBigInt.prototype.subtract = function (v) { + return new NativeBigInt(this.value - parseValue(v).value); + } + NativeBigInt.prototype.minus = NativeBigInt.prototype.subtract; + + BigInteger.prototype.negate = function () { + return new BigInteger(this.value, !this.sign); + }; + SmallInteger.prototype.negate = function () { + var sign = this.sign; + var small = new SmallInteger(-this.value); + small.sign = !sign; + return small; + }; + NativeBigInt.prototype.negate = function () { + return new NativeBigInt(-this.value); + } + + BigInteger.prototype.abs = function () { + return new BigInteger(this.value, false); + }; + SmallInteger.prototype.abs = function () { + return new SmallInteger(Math.abs(this.value)); + }; + NativeBigInt.prototype.abs = function () { + return new NativeBigInt(this.value >= 0 ? this.value : -this.value); + } + + + function multiplyLong(a, b) { + var a_l = a.length, + b_l = b.length, + l = a_l + b_l, + r = createArray(l), + base = BASE, + product, carry, i, a_i, b_j; + for (i = 0; i < a_l; ++i) { + a_i = a[i]; + for (var j = 0; j < b_l; ++j) { + b_j = b[j]; + product = a_i * b_j + r[i + j]; + carry = Math.floor(product / base); + r[i + j] = product - carry * base; + r[i + j + 1] += carry; + } + } + trim(r); + return r; + } + + function multiplySmall(a, b) { // assumes a is array, b is number with |b| < BASE + var l = a.length, + r = new Array(l), + base = BASE, + carry = 0, + product, i; + for (i = 0; i < l; i++) { + product = a[i] * b + carry; + carry = Math.floor(product / base); + r[i] = product - carry * base; + } + while (carry > 0) { + r[i++] = carry % base; + carry = Math.floor(carry / base); + } + return r; + } + + function shiftLeft(x, n) { + var r = []; + while (n-- > 0) r.push(0); + return r.concat(x); + } + + function multiplyKaratsuba(x, y) { + var n = Math.max(x.length, y.length); + + if (n <= 30) return multiplyLong(x, y); + n = Math.ceil(n / 2); + + var b = x.slice(n), + a = x.slice(0, n), + d = y.slice(n), + c = y.slice(0, n); + + var ac = multiplyKaratsuba(a, c), + bd = multiplyKaratsuba(b, d), + abcd = multiplyKaratsuba(addAny(a, b), addAny(c, d)); + + var product = addAny(addAny(ac, shiftLeft(subtract(subtract(abcd, ac), bd), n)), shiftLeft(bd, 2 * n)); + trim(product); + return product; + } + + // The following function is derived from a surface fit of a graph plotting the performance difference + // between long multiplication and karatsuba multiplication versus the lengths of the two arrays. + function useKaratsuba(l1, l2) { + return -0.012 * l1 - 0.012 * l2 + 0.000015 * l1 * l2 > 0; + } + + BigInteger.prototype.multiply = function (v) { + var n = parseValue(v), + a = this.value, b = n.value, + sign = this.sign !== n.sign, + abs; + if (n.isSmall) { + if (b === 0) return Integer[0]; + if (b === 1) return this; + if (b === -1) return this.negate(); + abs = Math.abs(b); + if (abs < BASE) { + return new BigInteger(multiplySmall(a, abs), sign); + } + b = smallToArray(abs); + } + if (useKaratsuba(a.length, b.length)) // Karatsuba is only faster for certain array sizes + return new BigInteger(multiplyKaratsuba(a, b), sign); + return new BigInteger(multiplyLong(a, b), sign); + }; + + BigInteger.prototype.times = BigInteger.prototype.multiply; + + function multiplySmallAndArray(a, b, sign) { // a >= 0 + if (a < BASE) { + return new BigInteger(multiplySmall(b, a), sign); + } + return new BigInteger(multiplyLong(b, smallToArray(a)), sign); + } + SmallInteger.prototype._multiplyBySmall = function (a) { + if (isPrecise(a.value * this.value)) { + return new SmallInteger(a.value * this.value); + } + return multiplySmallAndArray(Math.abs(a.value), smallToArray(Math.abs(this.value)), this.sign !== a.sign); + }; + BigInteger.prototype._multiplyBySmall = function (a) { + if (a.value === 0) return Integer[0]; + if (a.value === 1) return this; + if (a.value === -1) return this.negate(); + return multiplySmallAndArray(Math.abs(a.value), this.value, this.sign !== a.sign); + }; + SmallInteger.prototype.multiply = function (v) { + return parseValue(v)._multiplyBySmall(this); + }; + SmallInteger.prototype.times = SmallInteger.prototype.multiply; + + NativeBigInt.prototype.multiply = function (v) { + return new NativeBigInt(this.value * parseValue(v).value); + } + NativeBigInt.prototype.times = NativeBigInt.prototype.multiply; + + function square(a) { + //console.assert(2 * BASE * BASE < MAX_INT); + var l = a.length, + r = createArray(l + l), + base = BASE, + product, carry, i, a_i, a_j; + for (i = 0; i < l; i++) { + a_i = a[i]; + carry = 0 - a_i * a_i; + for (var j = i; j < l; j++) { + a_j = a[j]; + product = 2 * (a_i * a_j) + r[i + j] + carry; + carry = Math.floor(product / base); + r[i + j] = product - carry * base; + } + r[i + l] = carry; + } + trim(r); + return r; + } + + BigInteger.prototype.square = function () { + return new BigInteger(square(this.value), false); + }; + + SmallInteger.prototype.square = function () { + var value = this.value * this.value; + if (isPrecise(value)) return new SmallInteger(value); + return new BigInteger(square(smallToArray(Math.abs(this.value))), false); + }; + + NativeBigInt.prototype.square = function (v) { + return new NativeBigInt(this.value * this.value); + } + + function divMod1(a, b) { // Left over from previous version. Performs faster than divMod2 on smaller input sizes. + var a_l = a.length, + b_l = b.length, + base = BASE, + result = createArray(b.length), + divisorMostSignificantDigit = b[b_l - 1], + // normalization + lambda = Math.ceil(base / (2 * divisorMostSignificantDigit)), + remainder = multiplySmall(a, lambda), + divisor = multiplySmall(b, lambda), + quotientDigit, shift, carry, borrow, i, l, q; + if (remainder.length <= a_l) remainder.push(0); + divisor.push(0); + divisorMostSignificantDigit = divisor[b_l - 1]; + for (shift = a_l - b_l; shift >= 0; shift--) { + quotientDigit = base - 1; + if (remainder[shift + b_l] !== divisorMostSignificantDigit) { + quotientDigit = Math.floor((remainder[shift + b_l] * base + remainder[shift + b_l - 1]) / divisorMostSignificantDigit); + } + // quotientDigit <= base - 1 + carry = 0; + borrow = 0; + l = divisor.length; + for (i = 0; i < l; i++) { + carry += quotientDigit * divisor[i]; + q = Math.floor(carry / base); + borrow += remainder[shift + i] - (carry - q * base); + carry = q; + if (borrow < 0) { + remainder[shift + i] = borrow + base; + borrow = -1; + } else { + remainder[shift + i] = borrow; + borrow = 0; + } + } + while (borrow !== 0) { + quotientDigit -= 1; + carry = 0; + for (i = 0; i < l; i++) { + carry += remainder[shift + i] - base + divisor[i]; + if (carry < 0) { + remainder[shift + i] = carry + base; + carry = 0; + } else { + remainder[shift + i] = carry; + carry = 1; + } + } + borrow += carry; + } + result[shift] = quotientDigit; + } + // denormalization + remainder = divModSmall(remainder, lambda)[0]; + return [arrayToSmall(result), arrayToSmall(remainder)]; + } + + function divMod2(a, b) { // Implementation idea shamelessly stolen from Silent Matt's library http://silentmatt.com/biginteger/ + // Performs faster than divMod1 on larger input sizes. + var a_l = a.length, + b_l = b.length, + result = [], + part = [], + base = BASE, + guess, xlen, highx, highy, check; + while (a_l) { + part.unshift(a[--a_l]); + trim(part); + if (compareAbs(part, b) < 0) { + result.push(0); + continue; + } + xlen = part.length; + highx = part[xlen - 1] * base + part[xlen - 2]; + highy = b[b_l - 1] * base + b[b_l - 2]; + if (xlen > b_l) { + highx = (highx + 1) * base; + } + guess = Math.ceil(highx / highy); + do { + check = multiplySmall(b, guess); + if (compareAbs(check, part) <= 0) break; + guess--; + } while (guess); + result.push(guess); + part = subtract(part, check); + } + result.reverse(); + return [arrayToSmall(result), arrayToSmall(part)]; + } + + function divModSmall(value, lambda) { + var length = value.length, + quotient = createArray(length), + base = BASE, + i, q, remainder, divisor; + remainder = 0; + for (i = length - 1; i >= 0; --i) { + divisor = remainder * base + value[i]; + q = truncate(divisor / lambda); + remainder = divisor - q * lambda; + quotient[i] = q | 0; + } + return [quotient, remainder | 0]; + } + + function divModAny(self, v) { + var value, n = parseValue(v); + if (supportsNativeBigInt) { + return [new NativeBigInt(self.value / n.value), new NativeBigInt(self.value % n.value)]; + } + var a = self.value, b = n.value; + var quotient; + if (b === 0) throw new Error("Cannot divide by zero"); + if (self.isSmall) { + if (n.isSmall) { + return [new SmallInteger(truncate(a / b)), new SmallInteger(a % b)]; + } + return [Integer[0], self]; + } + if (n.isSmall) { + if (b === 1) return [self, Integer[0]]; + if (b == -1) return [self.negate(), Integer[0]]; + var abs = Math.abs(b); + if (abs < BASE) { + value = divModSmall(a, abs); + quotient = arrayToSmall(value[0]); + var remainder = value[1]; + if (self.sign) remainder = -remainder; + if (typeof quotient === "number") { + if (self.sign !== n.sign) quotient = -quotient; + return [new SmallInteger(quotient), new SmallInteger(remainder)]; + } + return [new BigInteger(quotient, self.sign !== n.sign), new SmallInteger(remainder)]; + } + b = smallToArray(abs); + } + var comparison = compareAbs(a, b); + if (comparison === -1) return [Integer[0], self]; + if (comparison === 0) return [Integer[self.sign === n.sign ? 1 : -1], Integer[0]]; + + // divMod1 is faster on smaller input sizes + if (a.length + b.length <= 200) + value = divMod1(a, b); + else value = divMod2(a, b); + + quotient = value[0]; + var qSign = self.sign !== n.sign, + mod = value[1], + mSign = self.sign; + if (typeof quotient === "number") { + if (qSign) quotient = -quotient; + quotient = new SmallInteger(quotient); + } else quotient = new BigInteger(quotient, qSign); + if (typeof mod === "number") { + if (mSign) mod = -mod; + mod = new SmallInteger(mod); + } else mod = new BigInteger(mod, mSign); + return [quotient, mod]; + } + + BigInteger.prototype.divmod = function (v) { + var result = divModAny(this, v); + return { + quotient: result[0], + remainder: result[1] + }; + }; + NativeBigInt.prototype.divmod = SmallInteger.prototype.divmod = BigInteger.prototype.divmod; + + + BigInteger.prototype.divide = function (v) { + return divModAny(this, v)[0]; + }; + NativeBigInt.prototype.over = NativeBigInt.prototype.divide = function (v) { + return new NativeBigInt(this.value / parseValue(v).value); + }; + SmallInteger.prototype.over = SmallInteger.prototype.divide = BigInteger.prototype.over = BigInteger.prototype.divide; + + BigInteger.prototype.mod = function (v) { + return divModAny(this, v)[1]; + }; + NativeBigInt.prototype.mod = NativeBigInt.prototype.remainder = function (v) { + return new NativeBigInt(this.value % parseValue(v).value); + }; + SmallInteger.prototype.remainder = SmallInteger.prototype.mod = BigInteger.prototype.remainder = BigInteger.prototype.mod; + + BigInteger.prototype.pow = function (v) { + var n = parseValue(v), + a = this.value, + b = n.value, + value, x, y; + if (b === 0) return Integer[1]; + if (a === 0) return Integer[0]; + if (a === 1) return Integer[1]; + if (a === -1) return n.isEven() ? Integer[1] : Integer[-1]; + if (n.sign) { + return Integer[0]; + } + if (!n.isSmall) throw new Error("The exponent " + n.toString() + " is too large."); + if (this.isSmall) { + if (isPrecise(value = Math.pow(a, b))) + return new SmallInteger(truncate(value)); + } + x = this; + y = Integer[1]; + while (true) { + if (b & 1 === 1) { + y = y.times(x); + --b; + } + if (b === 0) break; + b /= 2; + x = x.square(); + } + return y; + }; + SmallInteger.prototype.pow = BigInteger.prototype.pow; + + NativeBigInt.prototype.pow = function (v) { + var n = parseValue(v); + var a = this.value, b = n.value; + var _0 = BigInt(0), _1 = BigInt(1), _2 = BigInt(2); + if (b === _0) return Integer[1]; + if (a === _0) return Integer[0]; + if (a === _1) return Integer[1]; + if (a === BigInt(-1)) return n.isEven() ? Integer[1] : Integer[-1]; + if (n.isNegative()) return new NativeBigInt(_0); + var x = this; + var y = Integer[1]; + while (true) { + if ((b & _1) === _1) { + y = y.times(x); + --b; + } + if (b === _0) break; + b /= _2; + x = x.square(); + } + return y; + } + + BigInteger.prototype.modPow = function (exp, mod) { + exp = parseValue(exp); + mod = parseValue(mod); + if (mod.isZero()) throw new Error("Cannot take modPow with modulus 0"); + var r = Integer[1], + base = this.mod(mod); + if (exp.isNegative()) { + exp = exp.multiply(Integer[-1]); + base = base.modInv(mod); + } + while (exp.isPositive()) { + if (base.isZero()) return Integer[0]; + if (exp.isOdd()) r = r.multiply(base).mod(mod); + exp = exp.divide(2); + base = base.square().mod(mod); + } + return r; + }; + NativeBigInt.prototype.modPow = SmallInteger.prototype.modPow = BigInteger.prototype.modPow; + + function compareAbs(a, b) { + if (a.length !== b.length) { + return a.length > b.length ? 1 : -1; + } + for (var i = a.length - 1; i >= 0; i--) { + if (a[i] !== b[i]) return a[i] > b[i] ? 1 : -1; + } + return 0; + } + + BigInteger.prototype.compareAbs = function (v) { + var n = parseValue(v), + a = this.value, + b = n.value; + if (n.isSmall) return 1; + return compareAbs(a, b); + }; + SmallInteger.prototype.compareAbs = function (v) { + var n = parseValue(v), + a = Math.abs(this.value), + b = n.value; + if (n.isSmall) { + b = Math.abs(b); + return a === b ? 0 : a > b ? 1 : -1; + } + return -1; + }; + NativeBigInt.prototype.compareAbs = function (v) { + var a = this.value; + var b = parseValue(v).value; + a = a >= 0 ? a : -a; + b = b >= 0 ? b : -b; + return a === b ? 0 : a > b ? 1 : -1; + } + + BigInteger.prototype.compare = function (v) { + // See discussion about comparison with Infinity: + // https://github.com/peterolson/BigInteger.js/issues/61 + if (v === Infinity) { + return -1; + } + if (v === -Infinity) { + return 1; + } + + var n = parseValue(v), + a = this.value, + b = n.value; + if (this.sign !== n.sign) { + return n.sign ? 1 : -1; + } + if (n.isSmall) { + return this.sign ? -1 : 1; + } + return compareAbs(a, b) * (this.sign ? -1 : 1); + }; + BigInteger.prototype.compareTo = BigInteger.prototype.compare; + + SmallInteger.prototype.compare = function (v) { + if (v === Infinity) { + return -1; + } + if (v === -Infinity) { + return 1; + } + + var n = parseValue(v), + a = this.value, + b = n.value; + if (n.isSmall) { + return a == b ? 0 : a > b ? 1 : -1; + } + if (a < 0 !== n.sign) { + return a < 0 ? -1 : 1; + } + return a < 0 ? 1 : -1; + }; + SmallInteger.prototype.compareTo = SmallInteger.prototype.compare; + + NativeBigInt.prototype.compare = function (v) { + if (v === Infinity) { + return -1; + } + if (v === -Infinity) { + return 1; + } + var a = this.value; + var b = parseValue(v).value; + return a === b ? 0 : a > b ? 1 : -1; + } + NativeBigInt.prototype.compareTo = NativeBigInt.prototype.compare; + + BigInteger.prototype.equals = function (v) { + return this.compare(v) === 0; + }; + NativeBigInt.prototype.eq = NativeBigInt.prototype.equals = SmallInteger.prototype.eq = SmallInteger.prototype.equals = BigInteger.prototype.eq = BigInteger.prototype.equals; + + BigInteger.prototype.notEquals = function (v) { + return this.compare(v) !== 0; + }; + NativeBigInt.prototype.neq = NativeBigInt.prototype.notEquals = SmallInteger.prototype.neq = SmallInteger.prototype.notEquals = BigInteger.prototype.neq = BigInteger.prototype.notEquals; + + BigInteger.prototype.greater = function (v) { + return this.compare(v) > 0; + }; + NativeBigInt.prototype.gt = NativeBigInt.prototype.greater = SmallInteger.prototype.gt = SmallInteger.prototype.greater = BigInteger.prototype.gt = BigInteger.prototype.greater; + + BigInteger.prototype.lesser = function (v) { + return this.compare(v) < 0; + }; + NativeBigInt.prototype.lt = NativeBigInt.prototype.lesser = SmallInteger.prototype.lt = SmallInteger.prototype.lesser = BigInteger.prototype.lt = BigInteger.prototype.lesser; + + BigInteger.prototype.greaterOrEquals = function (v) { + return this.compare(v) >= 0; + }; + NativeBigInt.prototype.geq = NativeBigInt.prototype.greaterOrEquals = SmallInteger.prototype.geq = SmallInteger.prototype.greaterOrEquals = BigInteger.prototype.geq = BigInteger.prototype.greaterOrEquals; + + BigInteger.prototype.lesserOrEquals = function (v) { + return this.compare(v) <= 0; + }; + NativeBigInt.prototype.leq = NativeBigInt.prototype.lesserOrEquals = SmallInteger.prototype.leq = SmallInteger.prototype.lesserOrEquals = BigInteger.prototype.leq = BigInteger.prototype.lesserOrEquals; + + BigInteger.prototype.isEven = function () { + return (this.value[0] & 1) === 0; + }; + SmallInteger.prototype.isEven = function () { + return (this.value & 1) === 0; + }; + NativeBigInt.prototype.isEven = function () { + return (this.value & BigInt(1)) === BigInt(0); + } + + BigInteger.prototype.isOdd = function () { + return (this.value[0] & 1) === 1; + }; + SmallInteger.prototype.isOdd = function () { + return (this.value & 1) === 1; + }; + NativeBigInt.prototype.isOdd = function () { + return (this.value & BigInt(1)) === BigInt(1); + } + + BigInteger.prototype.isPositive = function () { + return !this.sign; + }; + SmallInteger.prototype.isPositive = function () { + return this.value > 0; + }; + NativeBigInt.prototype.isPositive = SmallInteger.prototype.isPositive; + + BigInteger.prototype.isNegative = function () { + return this.sign; + }; + SmallInteger.prototype.isNegative = function () { + return this.value < 0; + }; + NativeBigInt.prototype.isNegative = SmallInteger.prototype.isNegative; + + BigInteger.prototype.isUnit = function () { + return false; + }; + SmallInteger.prototype.isUnit = function () { + return Math.abs(this.value) === 1; + }; + NativeBigInt.prototype.isUnit = function () { + return this.abs().value === BigInt(1); + } + + BigInteger.prototype.isZero = function () { + return false; + }; + SmallInteger.prototype.isZero = function () { + return this.value === 0; + }; + NativeBigInt.prototype.isZero = function () { + return this.value === BigInt(0); + } + + BigInteger.prototype.isDivisibleBy = function (v) { + var n = parseValue(v); + if (n.isZero()) return false; + if (n.isUnit()) return true; + if (n.compareAbs(2) === 0) return this.isEven(); + return this.mod(n).isZero(); + }; + NativeBigInt.prototype.isDivisibleBy = SmallInteger.prototype.isDivisibleBy = BigInteger.prototype.isDivisibleBy; + + function isBasicPrime(v) { + var n = v.abs(); + if (n.isUnit()) return false; + if (n.equals(2) || n.equals(3) || n.equals(5)) return true; + if (n.isEven() || n.isDivisibleBy(3) || n.isDivisibleBy(5)) return false; + if (n.lesser(49)) return true; + // we don't know if it's prime: let the other functions figure it out + } + + function millerRabinTest(n, a) { + var nPrev = n.prev(), + b = nPrev, + r = 0, + d, t, i, x; + while (b.isEven()) b = b.divide(2), r++; + next: for (i = 0; i < a.length; i++) { + if (n.lesser(a[i])) continue; + x = bigInt(a[i]).modPow(b, n); + if (x.isUnit() || x.equals(nPrev)) continue; + for (d = r - 1; d != 0; d--) { + x = x.square().mod(n); + if (x.isUnit()) return false; + if (x.equals(nPrev)) continue next; + } + return false; + } + return true; + } + + // Set "strict" to true to force GRH-supported lower bound of 2*log(N)^2 + BigInteger.prototype.isPrime = function (strict) { + var isPrime = isBasicPrime(this); + if (isPrime !== undefined) return isPrime; + var n = this.abs(); + var bits = n.bitLength(); + if (bits <= 64) + return millerRabinTest(n, [2, 3, 5, 7, 11, 13, 17, 19, 23, 29, 31, 37]); + var logN = Math.log(2) * bits.toJSNumber(); + var t = Math.ceil((strict === true) ? (2 * Math.pow(logN, 2)) : logN); + for (var a = [], i = 0; i < t; i++) { + a.push(bigInt(i + 2)); + } + return millerRabinTest(n, a); + }; + NativeBigInt.prototype.isPrime = SmallInteger.prototype.isPrime = BigInteger.prototype.isPrime; + + BigInteger.prototype.isProbablePrime = function (iterations, rng) { + var isPrime = isBasicPrime(this); + if (isPrime !== undefined) return isPrime; + var n = this.abs(); + var t = iterations === undefined ? 5 : iterations; + for (var a = [], i = 0; i < t; i++) { + a.push(bigInt.randBetween(2, n.minus(2), rng)); + } + return millerRabinTest(n, a); + }; + NativeBigInt.prototype.isProbablePrime = SmallInteger.prototype.isProbablePrime = BigInteger.prototype.isProbablePrime; + + BigInteger.prototype.modInv = function (n) { + var t = bigInt.zero, newT = bigInt.one, r = parseValue(n), newR = this.abs(), q, lastT, lastR; + while (!newR.isZero()) { + q = r.divide(newR); + lastT = t; + lastR = r; + t = newT; + r = newR; + newT = lastT.subtract(q.multiply(newT)); + newR = lastR.subtract(q.multiply(newR)); + } + if (!r.isUnit()) throw new Error(this.toString() + " and " + n.toString() + " are not co-prime"); + if (t.compare(0) === -1) { + t = t.add(n); + } + if (this.isNegative()) { + return t.negate(); + } + return t; + }; + + NativeBigInt.prototype.modInv = SmallInteger.prototype.modInv = BigInteger.prototype.modInv; + + BigInteger.prototype.next = function () { + var value = this.value; + if (this.sign) { + return subtractSmall(value, 1, this.sign); + } + return new BigInteger(addSmall(value, 1), this.sign); + }; + SmallInteger.prototype.next = function () { + var value = this.value; + if (value + 1 < MAX_INT) return new SmallInteger(value + 1); + return new BigInteger(MAX_INT_ARR, false); + }; + NativeBigInt.prototype.next = function () { + return new NativeBigInt(this.value + BigInt(1)); + } + + BigInteger.prototype.prev = function () { + var value = this.value; + if (this.sign) { + return new BigInteger(addSmall(value, 1), true); + } + return subtractSmall(value, 1, this.sign); + }; + SmallInteger.prototype.prev = function () { + var value = this.value; + if (value - 1 > -MAX_INT) return new SmallInteger(value - 1); + return new BigInteger(MAX_INT_ARR, true); + }; + NativeBigInt.prototype.prev = function () { + return new NativeBigInt(this.value - BigInt(1)); + } + + var powersOfTwo = [1]; + while (2 * powersOfTwo[powersOfTwo.length - 1] <= BASE) powersOfTwo.push(2 * powersOfTwo[powersOfTwo.length - 1]); + var powers2Length = powersOfTwo.length, highestPower2 = powersOfTwo[powers2Length - 1]; + + function shift_isSmall(n) { + return Math.abs(n) <= BASE; + } + + BigInteger.prototype.shiftLeft = function (v) { + var n = parseValue(v).toJSNumber(); + if (!shift_isSmall(n)) { + throw new Error(String(n) + " is too large for shifting."); + } + if (n < 0) return this.shiftRight(-n); + var result = this; + if (result.isZero()) return result; + while (n >= powers2Length) { + result = result.multiply(highestPower2); + n -= powers2Length - 1; + } + return result.multiply(powersOfTwo[n]); + }; + NativeBigInt.prototype.shiftLeft = SmallInteger.prototype.shiftLeft = BigInteger.prototype.shiftLeft; + + BigInteger.prototype.shiftRight = function (v) { + var remQuo; + var n = parseValue(v).toJSNumber(); + if (!shift_isSmall(n)) { + throw new Error(String(n) + " is too large for shifting."); + } + if (n < 0) return this.shiftLeft(-n); + var result = this; + while (n >= powers2Length) { + if (result.isZero() || (result.isNegative() && result.isUnit())) return result; + remQuo = divModAny(result, highestPower2); + result = remQuo[1].isNegative() ? remQuo[0].prev() : remQuo[0]; + n -= powers2Length - 1; + } + remQuo = divModAny(result, powersOfTwo[n]); + return remQuo[1].isNegative() ? remQuo[0].prev() : remQuo[0]; + }; + NativeBigInt.prototype.shiftRight = SmallInteger.prototype.shiftRight = BigInteger.prototype.shiftRight; + + function bitwise(x, y, fn) { + y = parseValue(y); + var xSign = x.isNegative(), ySign = y.isNegative(); + var xRem = xSign ? x.not() : x, + yRem = ySign ? y.not() : y; + var xDigit = 0, yDigit = 0; + var xDivMod = null, yDivMod = null; + var result = []; + while (!xRem.isZero() || !yRem.isZero()) { + xDivMod = divModAny(xRem, highestPower2); + xDigit = xDivMod[1].toJSNumber(); + if (xSign) { + xDigit = highestPower2 - 1 - xDigit; // two's complement for negative numbers + } + + yDivMod = divModAny(yRem, highestPower2); + yDigit = yDivMod[1].toJSNumber(); + if (ySign) { + yDigit = highestPower2 - 1 - yDigit; // two's complement for negative numbers + } + + xRem = xDivMod[0]; + yRem = yDivMod[0]; + result.push(fn(xDigit, yDigit)); + } + var sum = fn(xSign ? 1 : 0, ySign ? 1 : 0) !== 0 ? bigInt(-1) : bigInt(0); + for (var i = result.length - 1; i >= 0; i -= 1) { + sum = sum.multiply(highestPower2).add(bigInt(result[i])); + } + return sum; + } + + BigInteger.prototype.not = function () { + return this.negate().prev(); + }; + NativeBigInt.prototype.not = SmallInteger.prototype.not = BigInteger.prototype.not; + + BigInteger.prototype.and = function (n) { + return bitwise(this, n, function (a, b) { return a & b; }); + }; + NativeBigInt.prototype.and = SmallInteger.prototype.and = BigInteger.prototype.and; + + BigInteger.prototype.or = function (n) { + return bitwise(this, n, function (a, b) { return a | b; }); + }; + NativeBigInt.prototype.or = SmallInteger.prototype.or = BigInteger.prototype.or; + + BigInteger.prototype.xor = function (n) { + return bitwise(this, n, function (a, b) { return a ^ b; }); + }; + NativeBigInt.prototype.xor = SmallInteger.prototype.xor = BigInteger.prototype.xor; + + var LOBMASK_I = 1 << 30, LOBMASK_BI = (BASE & -BASE) * (BASE & -BASE) | LOBMASK_I; + function roughLOB(n) { // get lowestOneBit (rough) + // SmallInteger: return Min(lowestOneBit(n), 1 << 30) + // BigInteger: return Min(lowestOneBit(n), 1 << 14) [BASE=1e7] + var v = n.value, + x = typeof v === "number" ? v | LOBMASK_I : + typeof v === "bigint" ? v | BigInt(LOBMASK_I) : + v[0] + v[1] * BASE | LOBMASK_BI; + return x & -x; + } + + function integerLogarithm(value, base) { + if (base.compareTo(value) <= 0) { + var tmp = integerLogarithm(value, base.square(base)); + var p = tmp.p; + var e = tmp.e; + var t = p.multiply(base); + return t.compareTo(value) <= 0 ? { p: t, e: e * 2 + 1 } : { p: p, e: e * 2 }; + } + return { p: bigInt(1), e: 0 }; + } + + BigInteger.prototype.bitLength = function () { + var n = this; + if (n.compareTo(bigInt(0)) < 0) { + n = n.negate().subtract(bigInt(1)); + } + if (n.compareTo(bigInt(0)) === 0) { + return bigInt(0); + } + return bigInt(integerLogarithm(n, bigInt(2)).e).add(bigInt(1)); + } + NativeBigInt.prototype.bitLength = SmallInteger.prototype.bitLength = BigInteger.prototype.bitLength; + + function max(a, b) { + a = parseValue(a); + b = parseValue(b); + return a.greater(b) ? a : b; + } + function min(a, b) { + a = parseValue(a); + b = parseValue(b); + return a.lesser(b) ? a : b; + } + function gcd(a, b) { + a = parseValue(a).abs(); + b = parseValue(b).abs(); + if (a.equals(b)) return a; + if (a.isZero()) return b; + if (b.isZero()) return a; + var c = Integer[1], d, t; + while (a.isEven() && b.isEven()) { + d = min(roughLOB(a), roughLOB(b)); + a = a.divide(d); + b = b.divide(d); + c = c.multiply(d); + } + while (a.isEven()) { + a = a.divide(roughLOB(a)); + } + do { + while (b.isEven()) { + b = b.divide(roughLOB(b)); + } + if (a.greater(b)) { + t = b; b = a; a = t; + } + b = b.subtract(a); + } while (!b.isZero()); + return c.isUnit() ? a : a.multiply(c); + } + function lcm(a, b) { + a = parseValue(a).abs(); + b = parseValue(b).abs(); + return a.divide(gcd(a, b)).multiply(b); + } + function randBetween(a, b, rng) { + a = parseValue(a); + b = parseValue(b); + var usedRNG = rng || Math.random; + var low = min(a, b), high = max(a, b); + var range = high.subtract(low).add(1); + if (range.isSmall) return low.add(Math.floor(usedRNG() * range)); + var digits = toBase(range, BASE).value; + var result = [], restricted = true; + for (var i = 0; i < digits.length; i++) { + var top = restricted ? digits[i] : BASE; + var digit = truncate(usedRNG() * top); + result.push(digit); + if (digit < top) restricted = false; + } + return low.add(Integer.fromArray(result, BASE, false)); + } + + var parseBase = function (text, base, alphabet, caseSensitive) { + alphabet = alphabet || DEFAULT_ALPHABET; + text = String(text); + if (!caseSensitive) { + text = text.toLowerCase(); + alphabet = alphabet.toLowerCase(); + } + var length = text.length; + var i; + var absBase = Math.abs(base); + var alphabetValues = {}; + for (i = 0; i < alphabet.length; i++) { + alphabetValues[alphabet[i]] = i; + } + for (i = 0; i < length; i++) { + var c = text[i]; + if (c === "-") continue; + if (c in alphabetValues) { + if (alphabetValues[c] >= absBase) { + if (c === "1" && absBase === 1) continue; + throw new Error(c + " is not a valid digit in base " + base + "."); + } + } + } + base = parseValue(base); + var digits = []; + var isNegative = text[0] === "-"; + for (i = isNegative ? 1 : 0; i < text.length; i++) { + var c = text[i]; + if (c in alphabetValues) digits.push(parseValue(alphabetValues[c])); + else if (c === "<") { + var start = i; + do { i++; } while (text[i] !== ">" && i < text.length); + digits.push(parseValue(text.slice(start + 1, i))); + } + else throw new Error(c + " is not a valid character"); + } + return parseBaseFromArray(digits, base, isNegative); + }; + + function parseBaseFromArray(digits, base, isNegative) { + var val = Integer[0], pow = Integer[1], i; + for (i = digits.length - 1; i >= 0; i--) { + val = val.add(digits[i].times(pow)); + pow = pow.times(base); + } + return isNegative ? val.negate() : val; + } + + function stringify(digit, alphabet) { + alphabet = alphabet || DEFAULT_ALPHABET; + if (digit < alphabet.length) { + return alphabet[digit]; + } + return "<" + digit + ">"; + } + + function toBase(n, base) { + base = bigInt(base); + if (base.isZero()) { + if (n.isZero()) return { value: [0], isNegative: false }; + throw new Error("Cannot convert nonzero numbers to base 0."); + } + if (base.equals(-1)) { + if (n.isZero()) return { value: [0], isNegative: false }; + if (n.isNegative()) + return { + value: [].concat.apply([], Array.apply(null, Array(-n.toJSNumber())) + .map(Array.prototype.valueOf, [1, 0]) + ), + isNegative: false + }; + + var arr = Array.apply(null, Array(n.toJSNumber() - 1)) + .map(Array.prototype.valueOf, [0, 1]); + arr.unshift([1]); + return { + value: [].concat.apply([], arr), + isNegative: false + }; + } + + var neg = false; + if (n.isNegative() && base.isPositive()) { + neg = true; + n = n.abs(); + } + if (base.isUnit()) { + if (n.isZero()) return { value: [0], isNegative: false }; + + return { + value: Array.apply(null, Array(n.toJSNumber())) + .map(Number.prototype.valueOf, 1), + isNegative: neg + }; + } + var out = []; + var left = n, divmod; + while (left.isNegative() || left.compareAbs(base) >= 0) { + divmod = left.divmod(base); + left = divmod.quotient; + var digit = divmod.remainder; + if (digit.isNegative()) { + digit = base.minus(digit).abs(); + left = left.next(); + } + out.push(digit.toJSNumber()); + } + out.push(left.toJSNumber()); + return { value: out.reverse(), isNegative: neg }; + } + + function toBaseString(n, base, alphabet) { + var arr = toBase(n, base); + return (arr.isNegative ? "-" : "") + arr.value.map(function (x) { + return stringify(x, alphabet); + }).join(''); + } + + BigInteger.prototype.toArray = function (radix) { + return toBase(this, radix); + }; + + SmallInteger.prototype.toArray = function (radix) { + return toBase(this, radix); + }; + + NativeBigInt.prototype.toArray = function (radix) { + return toBase(this, radix); + }; + + BigInteger.prototype.toString = function (radix, alphabet) { + if (radix === undefined) radix = 10; + if (radix !== 10) return toBaseString(this, radix, alphabet); + var v = this.value, l = v.length, str = String(v[--l]), zeros = "0000000", digit; + while (--l >= 0) { + digit = String(v[l]); + str += zeros.slice(digit.length) + digit; + } + var sign = this.sign ? "-" : ""; + return sign + str; + }; + + SmallInteger.prototype.toString = function (radix, alphabet) { + if (radix === undefined) radix = 10; + if (radix != 10) return toBaseString(this, radix, alphabet); + return String(this.value); + }; + + NativeBigInt.prototype.toString = SmallInteger.prototype.toString; + + NativeBigInt.prototype.toJSON = BigInteger.prototype.toJSON = SmallInteger.prototype.toJSON = function () { return this.toString(); } + + BigInteger.prototype.valueOf = function () { + return parseInt(this.toString(), 10); + }; + BigInteger.prototype.toJSNumber = BigInteger.prototype.valueOf; + + SmallInteger.prototype.valueOf = function () { + return this.value; + }; + SmallInteger.prototype.toJSNumber = SmallInteger.prototype.valueOf; + NativeBigInt.prototype.valueOf = NativeBigInt.prototype.toJSNumber = function () { + return parseInt(this.toString(), 10); + } + + function parseStringValue(v) { + if (isPrecise(+v)) { + var x = +v; + if (x === truncate(x)) + return supportsNativeBigInt ? new NativeBigInt(BigInt(x)) : new SmallInteger(x); + throw new Error("Invalid integer: " + v); + } + var sign = v[0] === "-"; + if (sign) v = v.slice(1); + var split = v.split(/e/i); + if (split.length > 2) throw new Error("Invalid integer: " + split.join("e")); + if (split.length === 2) { + var exp = split[1]; + if (exp[0] === "+") exp = exp.slice(1); + exp = +exp; + if (exp !== truncate(exp) || !isPrecise(exp)) throw new Error("Invalid integer: " + exp + " is not a valid exponent."); + var text = split[0]; + var decimalPlace = text.indexOf("."); + if (decimalPlace >= 0) { + exp -= text.length - decimalPlace - 1; + text = text.slice(0, decimalPlace) + text.slice(decimalPlace + 1); + } + if (exp < 0) throw new Error("Cannot include negative exponent part for integers"); + text += (new Array(exp + 1)).join("0"); + v = text; + } + var isValid = /^([0-9][0-9]*)$/.test(v); + if (!isValid) throw new Error("Invalid integer: " + v); + if (supportsNativeBigInt) { + return new NativeBigInt(BigInt(sign ? "-" + v : v)); + } + var r = [], max = v.length, l = LOG_BASE, min = max - l; + while (max > 0) { + r.push(+v.slice(min, max)); + min -= l; + if (min < 0) min = 0; + max -= l; + } + trim(r); + return new BigInteger(r, sign); + } + + function parseNumberValue(v) { + if (supportsNativeBigInt) { + return new NativeBigInt(BigInt(v)); + } + if (isPrecise(v)) { + if (v !== truncate(v)) throw new Error(v + " is not an integer."); + return new SmallInteger(v); + } + return parseStringValue(v.toString()); + } + + function parseValue(v) { + if (typeof v === "number") { + return parseNumberValue(v); + } + if (typeof v === "string") { + return parseStringValue(v); + } + if (typeof v === "bigint") { + return new NativeBigInt(v); + } + return v; + } + // Pre-define numbers in range [-999,999] + for (var i = 0; i < 1000; i++) { + Integer[i] = parseValue(i); + if (i > 0) Integer[-i] = parseValue(-i); + } + // Backwards compatibility + Integer.one = Integer[1]; + Integer.zero = Integer[0]; + Integer.minusOne = Integer[-1]; + Integer.max = max; + Integer.min = min; + Integer.gcd = gcd; + Integer.lcm = lcm; + Integer.isInstance = function (x) { return x instanceof BigInteger || x instanceof SmallInteger || x instanceof NativeBigInt; }; + Integer.randBetween = randBetween; + + Integer.fromArray = function (digits, base, isNegative) { + return parseBaseFromArray(digits.map(parseValue), parseValue(base || 10), isNegative); + }; + + return Integer; +})(); + +// Node.js check +if (typeof module !== "undefined" && module.hasOwnProperty("exports")) { + module.exports = bigInt; +} + +//amd check +if (typeof define === "function" && define.amd) { + define( function () { + return bigInt; + }); +} diff --git a/ui/imports/Constants.qml b/ui/imports/Constants.qml index a9199cdd44..13ad9f106c 100644 --- a/ui/imports/Constants.qml +++ b/ui/imports/Constants.qml @@ -25,14 +25,13 @@ QtObject { readonly property string generatedWalletType: "generated" // Transaction states - readonly property string pending: "pending" - readonly property string confirmed: "confirmed" - readonly property string unknown: "unknown" - readonly property string addressRequested: "addressRequested" - readonly property string addressReceived: "addressReceived" - readonly property string declined: "declined" - readonly property string shared: "shared" - readonly property string failure: "failure" + readonly property int addressRequested: 1 + readonly property int declined: 2 + readonly property int addressReceived: 3 + readonly property int transactionRequested: 4 + readonly property int transactionDeclined: 5 + readonly property int pending: 6 + readonly property int confirmed: 7 readonly property var accountColors: [ "#9B832F", diff --git a/ui/imports/Utils.qml b/ui/imports/Utils.qml index b3b69c6ee5..991bb63147 100644 --- a/ui/imports/Utils.qml +++ b/ui/imports/Utils.qml @@ -2,8 +2,14 @@ pragma Singleton import QtQuick 2.13 import "../shared/xss.js" as XSS +import "./BigNumber/bignumber.js" as BigNumber QtObject { + function newBigNumber(number) { + // See here for docs: https://github.com/peterolson/BigInteger.js + return BigNumber.bigInt(number) + } + function isHex(value) { return /^(-0x|0x)?[0-9a-fA-F]*$/i.test(value) } diff --git a/ui/nim-status-client.pro b/ui/nim-status-client.pro index 77923a9487..33241fe8ad 100644 --- a/ui/nim-status-client.pro +++ b/ui/nim-status-client.pro @@ -167,6 +167,7 @@ DISTFILES += \ app/AppLayouts/Wallet/components/collectiblesComponents/CollectiblesModal.qml \ app/AppLayouts/Wallet/components/collectiblesComponents/CollectiblesModalContent.qml \ app/AppLayouts/Wallet/components/collectiblesComponents/collectiblesData.js \ + app/AppLayouts/Wallet/data/Tokens.qml \ fonts/InterStatus/InterStatus-Black.otf \ fonts/InterStatus/InterStatus-BlackItalic.otf \ fonts/InterStatus/InterStatus-Bold.otf \ diff --git a/ui/shared/Input.qml b/ui/shared/Input.qml index c6b1cd7e29..41a4650787 100644 --- a/ui/shared/Input.qml +++ b/ui/shared/Input.qml @@ -18,6 +18,7 @@ Item { property int iconHeight: 24 property int iconWidth: 24 property bool copyToClipboard: false + property bool readOnly: false readonly property bool hasIcon: icon.toString() !== "" readonly property var forceActiveFocus: function () { @@ -65,7 +66,7 @@ Item { if (!!validationError) { return Style.current.danger } - if (inputValue.focus) { + if (!inputBox.readOnly && inputValue.focus) { return Style.current.inputBorderFocus } return Style.current.transparent @@ -87,6 +88,7 @@ Item { leftPadding: inputBox.hasIcon ? iconWidth + 20 : Style.current.padding selectByMouse: true font.pixelSize: fontPixelSize + readOnly: inputBox.readOnly background: Rectangle { color: Style.current.transparent } diff --git a/ui/shared/RecipientSelector.qml b/ui/shared/RecipientSelector.qml index dc088ceddb..7328f8d978 100644 --- a/ui/shared/RecipientSelector.qml +++ b/ui/shared/RecipientSelector.qml @@ -13,7 +13,7 @@ Item { property alias label: txtLabel.text // If supplied, additional info will be displayed top-right in danger colour (red) property alias additionalInfo: txtAddlInfo.text - property var selectedRecipient: { } + property var selectedRecipient property bool readOnly: false height: (readOnly ? inpReadOnly.height : inpAddress.height) + txtLabel.height //% "Invalid ethereum address" @@ -106,7 +106,7 @@ Item { textField.verticalAlignment: TextField.AlignVCenter textField.font.pixelSize: 15 textField.color: Style.current.secondaryText - textField.readOnly: true + readOnly: true validationErrorAlignment: TextEdit.AlignRight validationErrorTopMargin: 8 customHeight: 56