diff --git a/.gitignore b/.gitignore index cdb7c1e1..c8351d3f 100644 --- a/.gitignore +++ b/.gitignore @@ -3,3 +3,11 @@ translations/*.qm build version.js +# IOS stuff below +moc_* +*.o +*.mak +*.build +*.xcodeproj +monero-wallet-gui_plugin_import.cpp +monero-wallet-gui_qml_plugin_import.cpp diff --git a/LeftPanel.qml b/LeftPanel.qml index 6f843fc7..e9fa7467 100644 --- a/LeftPanel.qml +++ b/LeftPanel.qml @@ -50,6 +50,7 @@ Rectangle { signal addressBookClicked() signal miningClicked() signal signClicked() + signal keysClicked() function selectItem(pos) { menuColumn.previousButton.checked = false @@ -69,6 +70,8 @@ Rectangle { width: (isMobile)? appWindow.width : 260 color: "#FFFFFF" + anchors.bottom: parent.bottom + anchors.top: parent.top // Item with monero logo Item { @@ -138,8 +141,8 @@ Rectangle { visible: !isMobile Item { anchors.verticalCenter: parent.verticalCenter - height: 26 - width: 50 + height: 26 * scaleRatio + width: 50 * scaleRatio Image { anchors.centerIn: parent @@ -228,7 +231,8 @@ Rectangle { Flickable { - contentHeight: 500 + id:flicker + contentHeight: 500 * scaleRatio anchors.fill: parent clip: true @@ -239,7 +243,7 @@ Rectangle { anchors.left: parent.left anchors.right: parent.right anchors.top: parent.top - + clip: true property var previousButton: transferButton // ------------- Dashboard tab --------------- @@ -393,6 +397,7 @@ Rectangle { // ------------- Mining tab --------------- MenuButton { id: miningButton + visible: !isAndroid && !isIOS anchors.left: parent.left anchors.right: parent.right text: qsTr("Mining") + translationManager.emptyString @@ -483,10 +488,33 @@ Rectangle { color: "#505050" height: 1 } + // ------------- Sign/verify tab --------------- + MenuButton { + id: keysButton + anchors.left: parent.left + anchors.right: parent.right + text: qsTr("Seed & Keys") + translationManager.emptyString + symbol: qsTr("Y") + translationManager.emptyString + dotColor: "#FFD781" + under: settingsButton + onClicked: { + parent.previousButton.checked = false + parent.previousButton = keysButton + panel.keysClicked() + } + } + Rectangle { + visible: settingsButton.present + anchors.left: parent.left + anchors.right: parent.right + anchors.leftMargin: 16 + color: "#505050" + height: 1 + } - } + } // Column - } + } // Flickable NetworkStatusItem { id: networkStatus @@ -494,6 +522,7 @@ Rectangle { anchors.right: parent.right anchors.bottom: (progressBar.visible)? progressBar.top : parent.bottom; connected: Wallet.ConnectionStatus_Disconnected + height: 58 * scaleRatio } ProgressBar { @@ -501,8 +530,9 @@ Rectangle { anchors.left: parent.left anchors.right: parent.right anchors.bottom: parent.bottom + height: 35 * scaleRatio } - } + } // menuRect diff --git a/MiddlePanel.qml b/MiddlePanel.qml index 404a9d40..a008a62a 100644 --- a/MiddlePanel.qml +++ b/MiddlePanel.qml @@ -29,6 +29,7 @@ import QtQml 2.0 import QtQuick 2.2 +// QtQuick.Controls 2.0 isn't stable enough yet. Needs more testing. //import QtQuick.Controls 2.0 import QtQuick.Controls 1.4 import QtQuick.Layouts 1.1 @@ -47,7 +48,7 @@ Rectangle { property string balanceText property string unlockedBalanceLabelText: qsTr("Unlocked Balance") + translationManager.emptyString property string unlockedBalanceText - property int minHeight: (appWindow.height > 800) ? appWindow.height : 800 + property int minHeight: (appWindow.height > 800) ? appWindow.height : 800 * scaleRatio // property int headerHeight: header.height property Transfer transferView: Transfer { } @@ -58,6 +59,7 @@ Rectangle { property Settings settingsView: Settings { } property Mining miningView: Mining { } property AddressBook addressBookView: AddressBook { } + property Keys keysView: Keys { } signal paymentClicked(string address, string paymentId, string amount, int mixinCount, int priority, string description) @@ -93,33 +95,6 @@ Rectangle { transferView.sendTo(address, paymentId, description); } - - // XXX: just for memo, to be removed - // states: [ - // State { - // name: "Dashboard" - // PropertyChanges { target: loader; source: "pages/Dashboard.qml" } - // }, State { - // name: "History" - // PropertyChanges { target: loader; source: "pages/History.qml" } - // }, State { - // name: "Transfer" - // PropertyChanges { target: loader; source: "pages/Transfer.qml" } - // }, State { - // name: "Receive" - // PropertyChanges { target: loader; source: "pages/Receive.qml" } - // }, State { - // name: "AddressBook" - // PropertyChanges { target: loader; source: "pages/AddressBook.qml" } - // }, State { - // name: "Settings" - // PropertyChanges { target: loader; source: "pages/Settings.qml" } - // }, State { - // name: "Mining" - // PropertyChanges { target: loader; source: "pages/Mining.qml" } - // } - // ] - states: [ State { name: "Dashboard" @@ -132,7 +107,7 @@ Rectangle { }, State { name: "Transfer" PropertyChanges { target: root; currentView: transferView } - PropertyChanges { target: mainFlickable; contentHeight: 1000 } + PropertyChanges { target: mainFlickable; contentHeight: 1000 * scaleRatio } }, State { name: "Receive" PropertyChanges { target: root; currentView: receiveView } @@ -152,11 +127,15 @@ Rectangle { }, State { name: "Settings" PropertyChanges { target: root; currentView: settingsView } - PropertyChanges { target: mainFlickable; contentHeight: 1200 } + PropertyChanges { target: mainFlickable; contentHeight: 1200 * scaleRatio } }, State { name: "Mining" PropertyChanges { target: root; currentView: miningView } PropertyChanges { target: mainFlickable; contentHeight: minHeight } + }, State { + name: "Keys" + PropertyChanges { target: root; currentView: keysView } + PropertyChanges { target: mainFlickable; contentHeight: minHeight + 200 * scaleRatio } } ] @@ -187,6 +166,11 @@ Rectangle { Layout.fillWidth: true Layout.fillHeight: true clip: true + + onFlickingChanged: { + releaseFocus(); + } + // Disabled scrollbars, gives crash on startup on windows // ScrollIndicator.vertical: ScrollIndicator { } // ScrollBar.vertical: ScrollBar { } // uncomment to test @@ -195,11 +179,7 @@ Rectangle { StackView { id: stackView initialItem: transferView - // anchors.topMargin: 30 - // Layout.fillWidth: true - // Layout.fillHeight: true anchors.fill:parent - // anchors.margins: 4 clip: true // otherwise animation will affect left panel delegate: StackViewDelegate { diff --git a/RightPanel.qml b/RightPanel.qml index c4626835..25befca2 100644 --- a/RightPanel.qml +++ b/RightPanel.qml @@ -38,121 +38,4 @@ Rectangle { id: root width: 330 color: "#FFFFFF" - - function updateTweets() { - tabView.twitter.item.updateTweets() - } - - - TabView { - id: tabView - anchors.left: parent.left - anchors.right: parent.right - anchors.top: parent.top - anchors.bottom: styledRow.top - anchors.leftMargin: 14 - anchors.rightMargin: 14 - anchors.topMargin: 40 - property alias twitter: twitter - - - - - Tab { id: twitter; title: qsTr("Twitter"); source: "tabs/Twitter.qml" } - Tab { title: qsTr("News") + translationManager.emptyString } - Tab { title: qsTr("Help") + translationManager.emptyString } - Tab { title: qsTr("About") + translationManager.emptyString } - - - - style: TabViewStyle { - frameOverlap: 0 - tabOverlap: 0 - - tab: Rectangle { - implicitHeight: 31 - implicitWidth: styleData.index === tabView.count - 1 ? tabView.width - (tabView.count - 1) * 68 : 68 - - Text { - anchors.verticalCenter: parent.verticalCenter - anchors.left: parent.left - anchors.right: parent.right - anchors.leftMargin: 12 - anchors.rightMargin: 12 - elide: Text.ElideRight - font.family: "Arial" - font.pixelSize: 14 - color: styleData.selected ? "#FF4E40" : "#4A4646" - text: styleData.title - } - - Rectangle { - anchors.top: parent.top - anchors.bottom: parent.bottom - anchors.right: parent.right - width: 1 - color: "#DBDBDB" - visible: styleData.index !== tabView.count - 1 - } - - Rectangle { - anchors.left: parent.left - anchors.right: parent.right - anchors.bottom: parent.bottom - anchors.bottomMargin: -1 - height: 1 - color: styleData.selected ? "#FFFFFF" : "#DBDBDB" - } - } - - frame: Rectangle { - color: "#FFFFFF" - anchors.fill: parent - Rectangle { - anchors.left: parent.left - anchors.right: parent.right - anchors.top: parent.top - //anchors.topMargin: 1 - height: 1 - color: "#DBDBDB" - } - } - } - } - - Row { - id: styledRow - anchors.left: parent.left - anchors.right: parent.right - anchors.bottom: parent.bottom - - Rectangle { height: 8; width: parent.width / 5; color: "#FFE00A" } - Rectangle { height: 8; width: parent.width / 5; color: "#6B0072" } - Rectangle { height: 8; width: parent.width / 5; color: "#FF6C3C" } - Rectangle { height: 8; width: parent.width / 5; color: "#FFD781" } - Rectangle { height: 8; width: parent.width / 5 - 30; color: "#FF4F41" } - } - - Rectangle { - anchors.top: parent.top - anchors.left: parent.left - anchors.right: parent.right - height: 1 - color: "#DBDBDB" - } - - Rectangle { - anchors.top: parent.top - anchors.bottom: parent.bottom - anchors.right: parent.right - width: 1 - color: "#DBDBDB" - } - - // indicate disabled state -// Desaturate { -// anchors.fill: parent -// source: parent -// desaturation: root.enabled ? 0.0 : 1.0 -// } } diff --git a/components/AddressBookTable.qml b/components/AddressBookTable.qml index abf72371..4d281440 100644 --- a/components/AddressBookTable.qml +++ b/components/AddressBookTable.qml @@ -144,8 +144,10 @@ ListView { onOptionClicked: { // Ensure tooltip is closed appWindow.toolTip.visible = false; - if(option === 0) + if(option === 0) { clipboard.setText(address) + appWindow.showStatusMessage(qsTr("Address copied to clipboard"),3) + } else if(option === 1){ console.log("Sending to: ", address +" "+ paymentId); middlePanel.sendTo(address, paymentId, description); diff --git a/components/CheckBox.qml b/components/CheckBox.qml index ffe585c3..267106b1 100644 --- a/components/CheckBox.qml +++ b/components/CheckBox.qml @@ -29,61 +29,70 @@ import QtQuick 2.0 import QtQuick.Layouts 1.1 -Item { +RowLayout { id: checkBox property alias text: label.text property string checkedIcon property string uncheckedIcon property bool checked: false property alias background: backgroundRect.color - property int fontSize: 14 + property int fontSize: 14 * scaleRatio property alias fontColor: label.color signal clicked() - height: 25 - width: label.x + label.width - Layout.minimumWidth: label.x + label.contentWidth - clip: true + height: 25 * scaleRatio - Rectangle { - anchors.left: parent.left - height: parent.height - 1 - width: 25 - //radius: 4 - y: 0 - color: "#DBDBDB" + function toggle(){ + checkBox.checked = !checkBox.checked + checkBox.clicked() } - Rectangle { - id: backgroundRect - anchors.left: parent.left - height: parent.height - 1 - width: 25 - //radius: 4 - y: 1 - color: "#FFFFFF" - - Image { - anchors.centerIn: parent - source: checkBox.checked ? checkBox.checkedIcon : - checkBox.uncheckedIcon + RowLayout { + Layout.fillWidth: true + Rectangle { + anchors.left: parent.left + width: 25 * scaleRatio + height: checkBox.height - 1 + //radius: 4 + y: 0 + color: "#DBDBDB" } - } - Text { - id: label - anchors.verticalCenter: parent.verticalCenter - anchors.left: parent.left - anchors.leftMargin: 25 + 12 - font.family: "Arial" - font.pixelSize: checkBox.fontSize - color: "#525252" - } + Rectangle { + id: backgroundRect + anchors.left: parent.left + width: 25 * scaleRatio + height: checkBox.height - 1 + //radius: 4 + y: 1 + color: "#FFFFFF" - MouseArea { - anchors.fill: parent - onClicked: { - checkBox.checked = !checkBox.checked - checkBox.clicked() + Image { + anchors.centerIn: parent + source: checkBox.checked ? checkBox.checkedIcon : + checkBox.uncheckedIcon + } + + MouseArea { + anchors.fill: parent + onClicked: { + toggle() + } + } + } + + Text { + id: label + font.family: "Arial" + font.pixelSize: checkBox.fontSize + color: "#525252" + wrapMode: Text.Wrap + Layout.fillWidth: true + MouseArea { + anchors.fill: parent + onClicked: { + toggle() + } + } } } } diff --git a/components/DaemonManagerDialog.qml b/components/DaemonManagerDialog.qml index 77908b1f..009787eb 100644 --- a/components/DaemonManagerDialog.qml +++ b/components/DaemonManagerDialog.qml @@ -90,7 +90,7 @@ Window { } Text { - text: qsTr("Starting Monero daemon in %1 seconds").arg(countDown); + text: qsTr("Starting local node in %1 seconds").arg(countDown); font.pixelSize: 18 Layout.alignment: Qt.AlignHCenter Layout.fillWidth: true diff --git a/components/HistoryTable.qml b/components/HistoryTable.qml index 082822d3..d8024d18 100644 --- a/components/HistoryTable.qml +++ b/components/HistoryTable.qml @@ -79,14 +79,6 @@ ListView { } } - StandardDialog { - id: detailsPopup - cancelVisible: false - okVisible: true - width:850 - } - - delegate: Rectangle { id: delegate height: 144 @@ -111,10 +103,10 @@ ListView { onClicked: { var tx_key = currentWallet.getTxKey(hash) var tx_note = currentWallet.getUserNote(hash) - detailsPopup.title = "Transaction details"; - detailsPopup.content = buildTxDetailsString(hash,paymentId,tx_key,tx_note,destinations); - detailsPopup.open(); - + informationPopup.title = "Transaction details"; + informationPopup.content = buildTxDetailsString(hash,paymentId,tx_key,tx_note,destinations); + informationPopup.onCloseCallback = null + informationPopup.open(); } } @@ -383,7 +375,7 @@ ListView { Column { anchors.top: parent.top width: 148 - visible: isOut + visible: isOut && fee != "" Text { anchors.left: parent.left font.family: "Arial" diff --git a/components/HistoryTableMobile.qml b/components/HistoryTableMobile.qml new file mode 100644 index 00000000..87545755 --- /dev/null +++ b/components/HistoryTableMobile.qml @@ -0,0 +1,191 @@ +// Copyright (c) 2014-2017, The Monero Project +// +// All rights reserved. +// +// Redistribution and use in source and binary forms, with or without modification, are +// permitted provided that the following conditions are met: +// +// 1. Redistributions of source code must retain the above copyright notice, this list of +// conditions and the following disclaimer. +// +// 2. Redistributions in binary form must reproduce the above copyright notice, this list +// of conditions and the following disclaimer in the documentation and/or other +// materials provided with the distribution. +// +// 3. Neither the name of the copyright holder nor the names of its contributors may be +// used to endorse or promote products derived from this software without specific +// prior written permission. +// +// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY +// EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF +// MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL +// THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, +// PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS +// INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, +// STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF +// THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + +import QtQuick 2.0 +import QtQuick.Layouts 1.1 +import moneroComponents.Clipboard 1.0 +import moneroComponents.AddressBookModel 1.0 + +ListView { + id: listView + clip: true + boundsBehavior: ListView.StopAtBounds + property var previousItem + property var addressBookModel: null + + function buildTxDetailsString(tx_id, paymentId, tx_key,tx_note, destinations) { + var trStart = '', + trMiddle = '', + trEnd = ""; + + return '' + + (tx_id ? trStart + qsTr("Tx ID:") + trMiddle + tx_id + trEnd : "") + + (paymentId ? trStart + qsTr("Payment ID:") + trMiddle + paymentId + trEnd : "") + + (tx_key ? trStart + qsTr("Tx key:") + trMiddle + tx_key + trEnd : "") + + (tx_note ? trStart + qsTr("Tx note:") + trMiddle + tx_note + trEnd : "") + + (destinations ? trStart + qsTr("Destinations:") + trMiddle + destinations + trEnd : "") + + "
" + + translationManager.emptyString; + } + + function lookupPaymentID(paymentId) { + if (!addressBookModel) + return "" + var idx = addressBookModel.lookupPaymentID(paymentId) + if (idx < 0) + return "" + idx = addressBookModel.index(idx, 0) + return addressBookModel.data(idx, AddressBookModel.AddressBookDescriptionRole) + } + + + footer: Rectangle { + height: 127 * scaleRatio + width: listView.width + color: "#FFFFFF" + + Text { + anchors.centerIn: parent + font.family: "Arial" + font.pixelSize: 14 * scaleRatio + color: "#545454" + text: qsTr("No more results") + translationManager.emptyString + } + } + + delegate: Rectangle { + id: delegate + height: tableContent.height + 20 * scaleRatio + width: listView.width + color: index % 2 ? "#F8F8F8" : "#FFFFFF" + Layout.leftMargin: 10 * scaleRatio + z: listView.count - index + function collapseDropdown() { dropdown.expanded = false } + MouseArea { + anchors.fill: parent + onClicked: { + var tx_key = currentWallet.getTxKey(hash) + var tx_note = currentWallet.getUserNote(hash) + + informationPopup.title = "Transaction details"; + informationPopup.text = buildTxDetailsString(hash,paymentId,tx_key,tx_note,destinations); + informationPopup.open(); + informationPopup.onCloseCallback = null + } + } + + Rectangle { + anchors.right: parent.right + anchors.rightMargin: 15 * scaleRatio + anchors.top: parent.top + anchors.topMargin: parent.height/2 - this.height/2 + width: 30 * scaleRatio; height: 30 * scaleRatio + radius: 25 + color: "#FF4304" + + Image { + width: 20 * scaleRatio + height: 20 * scaleRatio + fillMode: Image.PreserveAspectFit + anchors.centerIn: parent + source: "qrc:///images/nextPage.png" + } + } + + ColumnLayout { + id: tableContent + // Date + RowLayout { + Layout.topMargin: 20 * scaleRatio + Layout.leftMargin: 10 * scaleRatio + Text { + font.family: "Arial" + font.pixelSize: 14 * scaleRatio + color: "#555555" + text: date + } + + Text { + font.family: "Arial" + font.pixelSize: 14 * scaleRatio + color: "#555555" + text: time + } + + // Show confirmations + Text { + visible: confirmations < confirmationsRequired || isPending + Layout.leftMargin: 5 * scaleRatio + font.family: "Arial" + font.pixelSize: 14 * scaleRatio + color: (confirmations < confirmationsRequired)? "#FF6C3C" : "#545454" + text: { + if (!isPending) + if(confirmations < confirmationsRequired) + return qsTr("(%1/%2 confirmations)").arg(confirmations).arg(confirmationsRequired) + if (!isOut) + return qsTr("UNCONFIRMED") + translationManager.emptyString + return qsTr("PENDING") + translationManager.emptyString + + } + } + } + + + // Amount & confirmations + RowLayout { + Layout.leftMargin: 10 * scaleRatio + spacing: 2 + Text { + font.family: "Arial" + font.pixelSize: 14 * scaleRatio + color: isOut ? "#FF4F41" : "#36B05B" + text: isOut ? "↓" : "↑" + } + + Text { + id: amountText + font.family: "Arial" + font.pixelSize: 18 * scaleRatio + color: isOut ? "#FF4F41" : "#36B05B" + text: displayAmount + } + } + } + } + + ListModel { + id: dropModel + ListElement { name: "Copy address to clipboard"; icon: "../images/dropdownCopy.png" } + ListElement { name: "Add to address book"; icon: "../images/dropdownAdd.png" } + ListElement { name: "Send to this address"; icon: "../images/dropdownSend.png" } + ListElement { name: "Find similar transactions"; icon: "../images/dropdownSearch.png" } + } + + Clipboard { id: clipboard } +} diff --git a/components/Input.qml b/components/Input.qml index 78bec8ea..1390ca17 100644 --- a/components/Input.qml +++ b/components/Input.qml @@ -33,7 +33,7 @@ import QtQuick 2.2 TextField { font.family: "Arial" horizontalAlignment: TextInput.AlignLeft - + selectByMouse: true style: TextFieldStyle { textColor: "#3F3F3F" placeholderTextColor: "#BABABA" diff --git a/components/Label.qml b/components/Label.qml index 30ff3d33..ac5c92ec 100644 --- a/components/Label.qml +++ b/components/Label.qml @@ -35,19 +35,21 @@ Item { property alias color: label.color property alias textFormat: label.textFormat property string tipText: "" - property int fontSize: 12 + property int fontSize: 16 * scaleRatio property alias wrapMode: label.wrapMode + property alias horizontalAlignment: label.horizontalAlignment signal linkActivated() - width: icon.x + icon.width - height: icon.height + width: icon.x + icon.width * scaleRatio + height: icon.height * scaleRatio + Layout.topMargin: 10 * scaleRatio Text { id: label anchors.bottom: parent.bottom - anchors.bottomMargin: 2 + anchors.bottomMargin: 2 * scaleRatio anchors.left: parent.left font.family: "Arial" - font.pixelSize: parent.fontSize + font.pixelSize: fontSize color: "#555555" onLinkActivated: item.linkActivated() } @@ -56,7 +58,7 @@ Item { id: icon anchors.verticalCenter: parent.verticalCenter anchors.left: label.right - anchors.leftMargin: 5 + anchors.leftMargin: 5 * scaleRatio source: "../images/whatIsIcon.png" visible: appWindow.whatIsEnable } diff --git a/components/LineEdit.qml b/components/LineEdit.qml index 3760fd2a..f7cb0612 100644 --- a/components/LineEdit.qml +++ b/components/LineEdit.qml @@ -36,13 +36,13 @@ Item { property alias readOnly : input.readOnly property alias cursorPosition: input.cursorPosition property alias echoMode: input.echoMode - property int fontSize: 18 + property int fontSize: 18 * scaleRatio property bool error: false signal editingFinished() signal accepted(); signal textUpdated(); - height: 37 + height: 37 * scaleRatio function getColor(error) { if (error) @@ -53,14 +53,14 @@ Item { Rectangle { anchors.fill: parent - anchors.bottomMargin: 1 + anchors.bottomMargin: 1 * scaleRatio color: "#DBDBDB" //radius: 4 } Rectangle { anchors.fill: parent - anchors.topMargin: 1 + anchors.topMargin: 1 * scaleRatio color: getColor(error) //radius: 4 } @@ -68,8 +68,8 @@ Item { Input { id: input anchors.fill: parent - anchors.leftMargin: 4 - anchors.rightMargin: 30 + anchors.leftMargin: 4 * scaleRatio + anchors.rightMargin: 30 * scaleRatio font.pixelSize: parent.fontSize onEditingFinished: item.editingFinished() onAccepted: item.accepted(); diff --git a/components/MenuButton.qml b/components/MenuButton.qml index a7baa019..23ab4f74 100644 --- a/components/MenuButton.qml +++ b/components/MenuButton.qml @@ -38,11 +38,18 @@ Rectangle { property var under: null signal clicked() + function doClick() { + // Android workaround + releaseFocus(); + clicked(); + } + + function getOffset() { var offset = 0 var item = button while (item.under) { - offset += 20 + offset += 20 * scaleRatio item = item.under } return offset @@ -50,7 +57,7 @@ Rectangle { color: checked ? "#FFFFFF" : "#1C1C1C" property bool present: !under || under.checked || checked || under.numSelectedChildren > 0 - height: present ? ((appWindow.height >= 800) ? 64 : 52) : 0 + height: present ? ((appWindow.height >= 800) ? 64 * scaleRatio : 52 * scaleRatio ) : 0 transform: Scale { yScale: button.present ? 1 : 0 @@ -76,18 +83,18 @@ Rectangle { anchors.bottom: parent.bottom anchors.left: parent.left anchors.leftMargin: parent.getOffset() - width: 50 + width: 50 * scaleRatio Rectangle { id: dot anchors.centerIn: parent - width: 16 + width: 16 * scaleRatio height: width radius: height / 2 Rectangle { anchors.centerIn: parent - width: 12 + width: 12 * scaleRatio height: width radius: height / 2 color: "#1C1C1C" @@ -98,7 +105,7 @@ Rectangle { Text { id: symbolText anchors.centerIn: parent - font.pixelSize: 11 + font.pixelSize: 11 * scaleRatio font.bold: true color: button.checked || buttonArea.containsMouse ? "#FFFFFF" : dot.color visible: appWindow.ctrlPressed @@ -117,7 +124,7 @@ Rectangle { Image { anchors.verticalCenter: parent.verticalCenter anchors.right: parent.right - anchors.rightMargin: 20 + anchors.rightMargin: 20 * scaleRatio anchors.leftMargin: parent.getOffset() source: "../images/menuIndicator.png" } @@ -126,9 +133,9 @@ Rectangle { id: label anchors.verticalCenter: parent.verticalCenter anchors.left: parent.left - anchors.leftMargin: parent.getOffset() + 50 + anchors.leftMargin: parent.getOffset() + 50 * scaleRatio font.family: "Arial" - font.pixelSize: 18 + font.pixelSize: 18 * scaleRatio color: parent.checked ? "#000000" : "#FFFFFF" } @@ -139,7 +146,7 @@ Rectangle { onClicked: { if(parent.checked) return - button.clicked() + button.doClick() parent.checked = true } } diff --git a/components/MobileHeader.qml b/components/MobileHeader.qml index 73b22430..0cd9c5e6 100644 --- a/components/MobileHeader.qml +++ b/components/MobileHeader.qml @@ -10,17 +10,16 @@ Rectangle { anchors.leftMargin: 1 anchors.rightMargin: 1 Layout.fillWidth: true - Layout.preferredHeight: 64 + Layout.preferredHeight: 64 * scaleRatio color: "#FFFFFF" -// visible: basicMode Image { id: logo - visible: appWindow.width > 460 + visible: appWindow.width > 460 * scaleRatio anchors.verticalCenter: parent.verticalCenter anchors.verticalCenterOffset: -5 anchors.left: parent.left - anchors.leftMargin: appWindow.persistentSettings.customDecorations ? 20 : 40 + anchors.leftMargin: 50 * scaleRatio source: "../images/moneroLogo2.png" } @@ -28,9 +27,8 @@ Rectangle { id: icon visible: !logo.visible anchors.verticalCenter: parent.verticalCenter -// anchors.verticalCenterOffset: -5 anchors.left: parent.left - anchors.leftMargin: appWindow.persistentSettings.customDecorations ? 20 : 40 + anchors.leftMargin: 40 * scaleRatio source: "../images/moneroIcon.png" } @@ -38,16 +36,16 @@ Rectangle { anchors.verticalCenter: parent.verticalCenter anchors.top: parent.top anchors.right: parent.right - anchors.topMargin: 10 - width: 256 + anchors.topMargin: 10 * scaleRatio + width: 256 * scaleRatio columns: 3 Text { id: balanceLabel - width: 116 - height: 20 + width: 116 * scaleRatio + height: 20 * scaleRatio font.family: "Arial" - font.pixelSize: 12 + font.pixelSize: 12 * scaleRatio font.letterSpacing: -1 elide: Text.ElideRight horizontalAlignment: Text.AlignLeft @@ -58,10 +56,10 @@ Rectangle { Text { id: balanceText - width: 110 - height: 20 + width: 110 * scaleRatio + height: 20 * scaleRatio font.family: "Arial" - font.pixelSize: 18 + font.pixelSize: 18 * scaleRatio font.letterSpacing: -1 elide: Text.ElideRight horizontalAlignment: Text.AlignLeft @@ -71,8 +69,8 @@ Rectangle { } Item { - height: 20 - width: 20 + height: 20 * scaleRatio + width: 20 * scaleRatio Image { anchors.verticalCenter: parent.verticalCenter @@ -82,10 +80,10 @@ Rectangle { } Text { - width: 116 - height: 20 + width: 116 * scaleRatio + height: 20 * scaleRatio font.family: "Arial" - font.pixelSize: 12 + font.pixelSize: 12 * scaleRatio font.letterSpacing: -1 elide: Text.ElideRight horizontalAlignment: Text.AlignLeft @@ -96,10 +94,10 @@ Rectangle { Text { id: availableBalanceText - width: 110 - height: 20 + width: 110 * scaleRatio + height: 20 * scaleRatio font.family: "Arial" - font.pixelSize: 14 + font.pixelSize: 14 * scaleRatio font.letterSpacing: -1 elide: Text.ElideRight horizontalAlignment: Text.AlignLeft diff --git a/components/NetworkStatusItem.qml b/components/NetworkStatusItem.qml index 5ba77585..7e6a5d1d 100644 --- a/components/NetworkStatusItem.qml +++ b/components/NetworkStatusItem.qml @@ -29,7 +29,7 @@ import QtQuick 2.0 import moneroComponents.Wallet 1.0 -Row { +Rectangle { id: item property var connected: Wallet.ConnectionStatus_Disconnected @@ -51,6 +51,8 @@ Row { if (status == Wallet.ConnectionStatus_Connected) { if(!appWindow.daemonSynced) return qsTr("Synchronizing") + if(appWindow.remoteNodeConnected) + return qsTr("Remote node") return qsTr("Connected") } if (status == Wallet.ConnectionStatus_WrongVersion) @@ -60,37 +62,44 @@ Row { return qsTr("Invalid connection status") } - Item { - id: iconItem - anchors.bottom: parent.bottom - width: 50 - height: 50 - Image { - anchors.centerIn: parent - source: getConnectionStatusImage(item.connected) + color: "#1C1C1C" + Row { + height: 60 * scaleRatio + Item { + id: iconItem + anchors.bottom: parent.bottom + width: 50 * scaleRatio + height: 50 * scaleRatio + + Image { + anchors.centerIn: parent + source: getConnectionStatusImage(item.connected) + } + } + + Column { + anchors.bottom: parent.bottom + height: 53 * scaleRatio + spacing: 3 * scaleRatio + + Text { + anchors.left: parent.left + font.family: "Arial" + font.pixelSize: 12 * scaleRatio + color: "#545454" + text: qsTr("Network status") + translationManager.emptyString + } + + Text { + anchors.left: parent.left + font.family: "Arial" + font.pixelSize: 18 * scaleRatio + color: getConnectionStatusColor(item.connected) + text: getConnectionStatusString(item.connected) + translationManager.emptyString + } } } - Column { - anchors.bottom: parent.bottom - height: 53 - spacing: 3 - Text { - anchors.left: parent.left - font.family: "Arial" - font.pixelSize: 12 - color: "#545454" - text: qsTr("Network status") + translationManager.emptyString - } - - Text { - anchors.left: parent.left - font.family: "Arial" - font.pixelSize: 18 - color: getConnectionStatusColor(item.connected) - text: getConnectionStatusString(item.connected) + translationManager.emptyString - } - } } diff --git a/components/PasswordDialog.qml b/components/PasswordDialog.qml index db9654cb..b90e192b 100644 --- a/components/PasswordDialog.qml +++ b/components/PasswordDialog.qml @@ -35,40 +35,48 @@ import QtQuick.Window 2.0 import "../components" as MoneroComponents -Window { +Item { id: root - modality: Qt.ApplicationModal - flags: Qt.Window | Qt.FramelessWindowHint + visible: false + Rectangle { + id: bg + z: parent.z + 1 + anchors.fill: parent + color: "white" + opacity: 0.9 + } + property alias password: passwordInput.text property string walletName // same signals as Dialog has signal accepted() signal rejected() - + signal closeCallback() function open(walletName) { root.walletName = walletName ? walletName : "" + leftPanel.enabled = false + middlePanel.enabled = false + titleBar.enabled = false show() + root.visible = true; + passwordInput.focus = true } - // TODO: implement without hardcoding sizes - width: 480 - height: walletName ? 240 : 200 - - // Make window draggable - MouseArea { - anchors.fill: parent - property point lastMousePos: Qt.point(0, 0) - onPressed: { lastMousePos = Qt.point(mouseX, mouseY); } - onMouseXChanged: root.x += (mouseX - lastMousePos.x) - onMouseYChanged: root.y += (mouseY - lastMousePos.y) + function close() { + leftPanel.enabled = true + middlePanel.enabled = true + titleBar.enabled = true + root.visible = false; + closeCallback(); } ColumnLayout { + z: bg.z + 1 id: mainLayout spacing: 10 - anchors { fill: parent; margins: 35 } + anchors { fill: parent; margins: 35 * scaleRatio } ColumnLayout { id: column @@ -81,20 +89,20 @@ Window { Layout.columnSpan: 2 Layout.fillWidth: true horizontalAlignment: Text.AlignHCenter - font.pixelSize: 24 + font.pixelSize: 18 * scaleRatio font.family: "Arial" color: "#555555" } TextField { id : passwordInput - focus: true Layout.fillWidth: true Layout.alignment: Qt.AlignHCenter + Layout.maximumWidth: 400 * scaleRatio horizontalAlignment: TextInput.AlignHCenter verticalAlignment: TextInput.AlignVCenter font.family: "Arial" - font.pixelSize: 32 + font.pixelSize: 32 * scaleRatio echoMode: TextInput.Password KeyNavigation.tab: okButton @@ -128,27 +136,18 @@ Window { Layout.fillWidth: true Layout.alignment: Qt.AlignHCenter anchors.bottomMargin: 3 + Layout.maximumWidth: passwordInput.width } - // padding - Rectangle { - Layout.fillWidth: true - Layout.alignment: Qt.AlignHCenter - height: 10 - opacity: 0 - color: "black" - } } // Ok/Cancel buttons RowLayout { id: buttons - spacing: 60 + spacing: 60 * scaleRatio Layout.alignment: Qt.AlignHCenter MoneroComponents.StandardButton { id: cancelButton - width: 120 - fontSize: 14 shadowReleasedColor: "#FF4304" shadowPressedColor: "#B32D00" releasedColor: "#FF6C3C" @@ -162,13 +161,11 @@ Window { } MoneroComponents.StandardButton { id: okButton - width: 120 - fontSize: 14 shadowReleasedColor: "#FF4304" shadowPressedColor: "#B32D00" releasedColor: "#FF6C3C" pressedColor: "#FF4304" - text: qsTr("Ok") + text: qsTr("Continue") KeyNavigation.tab: cancelButton onClicked: { root.close() @@ -178,6 +175,3 @@ Window { } } } - - - diff --git a/components/ProcessingSplash.qml b/components/ProcessingSplash.qml index 2f6725db..d7fbd5cb 100644 --- a/components/ProcessingSplash.qml +++ b/components/ProcessingSplash.qml @@ -31,17 +31,26 @@ import QtQuick.Window 2.1 import QtQuick.Controls 1.4 import QtQuick.Layouts 1.1 -Window { +Rectangle { id: root - modality: Qt.ApplicationModal - flags: Qt.Window + color: "white" + visible: false + z:11 property alias messageText: messageTitle.text property alias heightProgressText : heightProgress.text - width: 200 - height: 100 + width: 200 * scaleRatio + height: 100 * scaleRatio opacity: 0.7 + function show() { + root.visible = true; + } + + function close() { + root.visible = false; + } + ColumnLayout { id: rootLayout @@ -49,8 +58,8 @@ Window { anchors.right: parent.right anchors.verticalCenter: parent.verticalCenter - anchors.leftMargin: 30 - anchors.rightMargin: 30 + anchors.leftMargin: 30 * scaleRatio + anchors.rightMargin: 30 * scaleRatio BusyIndicator { running: parent.visible @@ -61,7 +70,7 @@ Window { id: messageTitle text: "Please wait..." font { - pixelSize: 22 + pixelSize: 22 * scaleRatio } horizontalAlignment: Text.AlignHCenter Layout.alignment: Qt.AlignVCenter | Qt.AlignHCenter @@ -72,7 +81,7 @@ Window { Text { id: heightProgress font { - pixelSize: 18 + pixelSize: 18 * scaleRatio } horizontalAlignment: Text.AlignHCenter Layout.alignment: Qt.AlignVCenter | Qt.AlignHCenter diff --git a/components/ProgressBar.qml b/components/ProgressBar.qml index 0c1519de..8f462122 100644 --- a/components/ProgressBar.qml +++ b/components/ProgressBar.qml @@ -29,15 +29,13 @@ import QtQuick 2.0 import moneroComponents.Wallet 1.0 -Item { +Rectangle { id: item property int fillLevel: 0 - height: 22 - anchors.margins:15 visible: false - //clip: true + color: "#1C1C1C" - function updateProgress(currentBlock,targetBlock, blocksToSync){ + function updateProgress(currentBlock,targetBlock, blocksToSync, statusTxt){ if(targetBlock == 1) { fillLevel = 0 progressText.text = qsTr("Establishing connection..."); @@ -54,53 +52,68 @@ Item { else var progressLevel = (100*(currentBlock/targetBlock)).toFixed(0); fillLevel = progressLevel - progressText.text = qsTr("Blocks remaining: %1").arg(remaining.toFixed(0)); + if(typeof statusTxt != "undefined" && statusTxt != "") { + progressText.text = statusTxt + (" %1").arg(remaining.toFixed(0)); + } else { + progressText.text = qsTr("Blocks remaining: %1").arg(remaining.toFixed(0)); + } + + + progressBar.visible = currentBlock < targetBlock } } - Rectangle { - id: bar - anchors.left: parent.left - anchors.right: parent.right - anchors.top: parent.top - height: 22 - radius: 2 - color: "#FFFFFF" - + Item { + anchors.leftMargin: 15 * scaleRatio + anchors.rightMargin: 15 * scaleRatio + anchors.fill: parent Rectangle { - id: fillRect + id: bar + anchors.left: parent.left + anchors.right: parent.right anchors.top: parent.top - anchors.bottom: parent.bottom - anchors.left: parent.left - anchors.margins: 2 - height: bar.height - property int maxWidth: parent.width - 4 - width: (maxWidth * fillLevel) / 100 - color: { - if(item.fillLevel < 99 ) return "#FF6C3C" - //if(item.fillLevel < 99) return "#FFE00A" - return "#36B25C" - } + height: 22 * scaleRatio + radius: 2 * scaleRatio + color: "#FFFFFF" - } - - Rectangle { - color:"#333" - anchors.bottom: parent.bottom - anchors.left: parent.left - anchors.leftMargin: 8 - - Text { - id:progressText + Rectangle { + id: fillRect + anchors.top: parent.top anchors.bottom: parent.bottom - font.family: "Arial" - font.pixelSize: 12 - color: "#000" - text: qsTr("Synchronizing blocks") - height:18 + anchors.left: parent.left + anchors.margins: 2 * scaleRatio + height: bar.height + property int maxWidth: parent.width - 4 * scaleRatio + width: (maxWidth * fillLevel) / 100 + color: { + if(item.fillLevel < 99 ) return "#FF6C3C" + //if(item.fillLevel < 99) return "#FFE00A" + return "#36B25C" + } + + } + + Rectangle { + color:"#333" + anchors.bottom: parent.bottom + anchors.left: parent.left + anchors.leftMargin: 8 * scaleRatio + + Text { + id:progressText + anchors.bottom: parent.bottom + font.family: "Arial" + font.pixelSize: 12 * scaleRatio + color: "#000" + text: qsTr("Synchronizing blocks") + height:18 * scaleRatio + } } } + } + + } diff --git a/components/QRCodeScanner.qml b/components/QRCodeScanner.qml index d9a6d933..669bd271 100644 --- a/components/QRCodeScanner.qml +++ b/components/QRCodeScanner.qml @@ -44,7 +44,7 @@ Rectangle { color: "black" state: "Stopped" - signal qrcode_decoded(string address, string payment_id, string amount, string tx_description, string recipient_name) + signal qrcode_decoded(string address, string payment_id, string amount, string tx_description, string recipient_name, var extra_parameters) states: [ State { @@ -83,7 +83,7 @@ Rectangle { id : finder objectName: "QrFinder" onDecoded : { - root.qrcode_decoded(address, payment_id, amount, tx_description, recipient_name) + root.qrcode_decoded(address, payment_id, amount, tx_description, recipient_name, extra_parameters) root.state = "Stopped" } onNotifyError : { @@ -126,7 +126,7 @@ Rectangle { MessageDialog { id: messageDialog - title: "Scanning QrCode" + title: qsTr("QrCode Scanned") + translationManager.emptyString onAccepted: { root.state = "Stopped" } diff --git a/components/RemoteNodeEdit.qml b/components/RemoteNodeEdit.qml new file mode 100644 index 00000000..83b059cd --- /dev/null +++ b/components/RemoteNodeEdit.qml @@ -0,0 +1,60 @@ +// Copyright (c) 2014-2017, The Monero Project +// +// All rights reserved. +// +// Redistribution and use in source and binary forms, with or without modification, are +// permitted provided that the following conditions are met: +// +// 1. Redistributions of source code must retain the above copyright notice, this list of +// conditions and the following disclaimer. +// +// 2. Redistributions in binary form must reproduce the above copyright notice, this list +// of conditions and the following disclaimer in the documentation and/or other +// materials provided with the distribution. +// +// 3. Neither the name of the copyright holder nor the names of its contributors may be +// used to endorse or promote products derived from this software without specific +// prior written permission. +// +// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY +// EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF +// MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL +// THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, +// PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS +// INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, +// STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF +// THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + +import QtQuick.Controls 1.2 +import QtQuick.Controls.Styles 1.2 +import QtQuick 2.2 +import QtQuick.Layouts 1.1 + +GridLayout { + columns: (isMobile) ? 1 : 2 + id: root + property alias daemonAddrText: daemonAddr.text + property alias daemonPortText: daemonPort.text + + signal editingFinished() + + function getAddress() { + return daemonAddr.text.trim() + ":" + daemonPort.text.trim() + } + + LineEdit { + id: daemonAddr + Layout.fillWidth: true + placeholderText: qsTr("Remote Node Hostname / IP") + translationManager.emptyString + onEditingFinished: root.editingFinished() + } + + + LineEdit { + id: daemonPort + Layout.fillWidth: true + placeholderText: qsTr("Port") + translationManager.emptyString + onEditingFinished: root.editingFinished() + } +} diff --git a/components/StandardButton.qml b/components/StandardButton.qml index 33cedee7..6f714947 100644 --- a/components/StandardButton.qml +++ b/components/StandardButton.qml @@ -31,19 +31,25 @@ import QtQuick.Layouts 1.1 Item { id: button - height: 37 - property string shadowPressedColor - property string shadowReleasedColor - property string pressedColor - property string releasedColor + height: 37 * scaleRatio + property string shadowPressedColor: "#B32D00" + property string shadowReleasedColor: "#FF4304" + property string pressedColor: "#FF4304" + property string releasedColor: "#FF6C3C" property string icon: "" property string textColor: "#FFFFFF" - property int fontSize: 12 + property int fontSize: 12 * scaleRatio property alias text: label.text signal clicked() // Dynamic label width - Layout.minimumWidth: (label.contentWidth > 80)? label.contentWidth + 20 : 100 + Layout.minimumWidth: (label.contentWidth > 50)? label.contentWidth + 10 : 60 + + function doClick() { + // Android workaround + releaseFocus(); + clicked(); + } Rectangle { anchors.left: parent.left @@ -98,9 +104,9 @@ Item { MouseArea { id: buttonArea anchors.fill: parent - onClicked: parent.clicked() + onClicked: doClick() } - Keys.onSpacePressed: clicked() - Keys.onReturnPressed: clicked() + Keys.onSpacePressed: doClick() + Keys.onReturnPressed: doClick() } diff --git a/components/StandardDialog.qml b/components/StandardDialog.qml index 681c2d3d..84402147 100644 --- a/components/StandardDialog.qml +++ b/components/StandardDialog.qml @@ -35,10 +35,10 @@ import QtQuick.Window 2.0 import "../components" as MoneroComponents -Window { +Rectangle { id: root - modality: Qt.ApplicationModal - flags: Qt.Window | Qt.FramelessWindowHint + color: "white" + visible: false property alias title: dialogTitle.text property alias text: dialogContent.text property alias content: root.text @@ -53,6 +53,7 @@ Window { // same signals as Dialog has signal accepted() signal rejected() + signal closeCallback(); // Make window draggable MouseArea { @@ -64,12 +65,24 @@ Window { } function open() { + // Center + if(!isMobile) { + root.x = parent.width/2 - root.width/2 + root.y = screenHeight/2 - root.height/2 + } show() + root.z = 11 + root.visible = true; + } + + function close() { + root.visible = false; + closeCallback(); } // TODO: implement without hardcoding sizes - width: 480 - height: 280 + width: isMobile ? screenWidth : 480 + height: isMobile ? screenHeight : 280 ColumnLayout { id: mainLayout @@ -84,7 +97,7 @@ Window { Label { id: dialogTitle horizontalAlignment: Text.AlignHCenter - font.pixelSize: 32 + font.pixelSize: 18 * scaleRatio font.family: "Arial" color: "#555555" } @@ -99,7 +112,23 @@ Window { font.family: "Arial" textFormat: TextEdit.AutoText readOnly: true - font.pixelSize: 12 + font.pixelSize: 12 * scaleRatio + selectByMouse: false + wrapMode: TextEdit.Wrap + + MouseArea { + anchors.fill: parent + onClicked: { + appWindow.showStatusMessage(qsTr("Double tap to copy"),3) + } + onDoubleClicked: { + parent.selectAll() + parent.copy() + parent.deselect() + console.log("copied to clipboard"); + appWindow.showStatusMessage(qsTr("Content copied to clipboard"),3) + } + } } } @@ -111,8 +140,6 @@ Window { MoneroComponents.StandardButton { id: cancelButton - width: 120 - fontSize: 14 shadowReleasedColor: "#FF4304" shadowPressedColor: "#B32D00" releasedColor: "#FF6C3C" @@ -126,13 +153,11 @@ Window { MoneroComponents.StandardButton { id: okButton - width: 120 - fontSize: 14 shadowReleasedColor: "#FF4304" shadowPressedColor: "#B32D00" releasedColor: "#FF6C3C" pressedColor: "#FF4304" - text: qsTr("Ok") + text: qsTr("OK") KeyNavigation.tab: cancelButton onClicked: { root.close() diff --git a/components/StandardDropdown.qml b/components/StandardDropdown.qml index 50b60fe0..5aac249b 100644 --- a/components/StandardDropdown.qml +++ b/components/StandardDropdown.qml @@ -38,7 +38,10 @@ Item { property string textColor: "#FFFFFF" property alias currentIndex: column.currentIndex property bool expanded: false - height: 37 + + signal changed(); + + height: 37 * scaleRatio onExpandedChanged: if(expanded) appWindow.currentItem = dropdown function hide() { dropdown.expanded = false } @@ -54,12 +57,18 @@ Item { return true } + // Workaroud for suspected memory leak in 5.8 causing malloc crash on app exit + function update() { + firstColText.text = column.currentIndex < repeater.model.rowCount() ? qsTr(repeater.model.get(column.currentIndex).column1) + translationManager.emptyString : "" + secondColText.text = column.currentIndex < repeater.model.rowCount() ? qsTr(repeater.model.get(column.currentIndex).column2) + translationManager.emptyString : "" + } + Item { id: head anchors.left: parent.left anchors.right: parent.right anchors.top: parent.top - height: 37 + height: 37 * scaleRatio Rectangle { anchors.left: parent.left @@ -82,8 +91,8 @@ Item { Rectangle { anchors.left: parent.left anchors.bottom: parent.bottom - height: 3 - width: 3 + height: 3 * scaleRatio + width: 3 * scaleRatio color: dropdown.pressedColor visible: dropdown.expanded || droplist.height > 0 } @@ -91,8 +100,8 @@ Item { Rectangle { anchors.right: parent.right anchors.bottom: parent.bottom - height: 3 - width: 3 + height: 3 * scaleRatio + width: 3 * scaleRatio color: dropdown.pressedColor visible: dropdown.expanded || droplist.height > 0 } @@ -101,26 +110,23 @@ Item { id: firstColText anchors.verticalCenter: parent.verticalCenter anchors.left: parent.left - anchors.leftMargin: 12 + anchors.leftMargin: 12 * scaleRatio elide: Text.ElideRight font.family: "Arial" font.bold: true - font.pixelSize: 12 + font.pixelSize: 12 * scaleRatio color: "#FFFFFF" - text: column.currentIndex < repeater.model.rowCount() ? qsTr(repeater.model.get(column.currentIndex).column1) + translationManager.emptyString : "" } Text { id: secondColText anchors.verticalCenter: parent.verticalCenter anchors.right: separator.left - anchors.rightMargin: 12 + anchors.rightMargin: 12 * scaleRatio width: dropdown.expanded ? w : (separator.x - 12) - (firstColText.x + firstColText.width + 5) font.family: "Arial" - font.pixelSize: 12 + font.pixelSize: 12 * scaleRatio color: "#FFFFFF" - text: column.currentIndex < repeater.model.rowCount() ? qsTr(repeater.model.get(column.currentIndex).column2) + translationManager.emptyString : "" - property int w: 0 Component.onCompleted: w = implicitWidth } @@ -129,7 +135,7 @@ Item { id: separator anchors.right: dropIndicator.left anchors.verticalCenter: parent.verticalCenter - height: 18 + height: 18 * scaleRatio width: 1 color: "#FFFFFF" } @@ -139,12 +145,12 @@ Item { anchors.top: parent.top anchors.bottom: parent.bottom anchors.right: parent.right - width: 32 + width: 32 * scaleRatio Image { anchors.centerIn: parent source: "../images/whiteDropIndicator.png" - rotation: dropdown.expanded ? 180 : 0 + rotation: dropdown.expanded ? 180 * scaleRatio : 0 } } @@ -168,14 +174,14 @@ Item { Rectangle { anchors.left: parent.left anchors.top: parent.top - width: 3; height: 3 + width: 3 * scaleRatio; height: 3 * scaleRatio color: dropdown.pressedColor } Rectangle { anchors.right: parent.right anchors.top: parent.top - width: 3; height: 3 + width: 3 * scaleRatio; height: 3 * scaleRatio color: dropdown.pressedColor } @@ -209,7 +215,7 @@ Item { delegate: Rectangle { anchors.left: parent.left anchors.right: parent.right - height: 30 + height: 30 * scaleRatio //radius: index === repeater.count - 1 ? 4 : 0 color: itemArea.containsMouse || index === column.currentIndex || itemArea.containsMouse ? dropdown.releasedColor : dropdown.pressedColor @@ -217,11 +223,11 @@ Item { anchors.verticalCenter: parent.verticalCenter anchors.left: parent.left anchors.right: col2Text.left - anchors.leftMargin: 12 - anchors.rightMargin: column2.length > 0 ? 12 : 0 + anchors.leftMargin: 12 * scaleRatio + anchors.rightMargin: column2.length > 0 ? 12 * scaleRatio: 0 font.family: "Arial" font.bold: true - font.pixelSize: 12 + font.pixelSize: 12 * scaleRatio color: "#FFFFFF" text: qsTr(column1) + translationManager.emptyString } @@ -230,9 +236,9 @@ Item { id: col2Text anchors.verticalCenter: parent.verticalCenter anchors.right: parent.right - anchors.rightMargin: 45 + anchors.rightMargin: 45 * scaleRatio font.family: "Arial" - font.pixelSize: 12 + font.pixelSize: 12 * scaleRatio color: "#FFFFFF" text: column2 } @@ -240,14 +246,14 @@ Item { Rectangle { anchors.left: parent.left anchors.top: parent.top - width: 3; height: 3 + width: 3 * scaleRatio; height: 3 * scaleRatio color: parent.color } Rectangle { anchors.right: parent.right anchors.top: parent.top - width: 3; height: 3 + width: 3 * scaleRatio; height: 3 * scaleRatio color: parent.color } @@ -258,6 +264,8 @@ Item { onClicked: { dropdown.expanded = false column.currentIndex = index + changed(); + dropdown.update() } } } diff --git a/components/TickDelegate.qml b/components/TickDelegate.qml index 3b256a2d..4e9789a4 100644 --- a/components/TickDelegate.qml +++ b/components/TickDelegate.qml @@ -49,7 +49,7 @@ Item { anchors.bottomMargin: 2 font.family: "Arial" font.bold: true - font.pixelSize: 12 + font.pixelSize: 12 * scaleRatio color: "#4A4949" text: { if(currentIndex === 0) return qsTr("Normal") + translationManager.emptyString diff --git a/components/TitleBar.qml b/components/TitleBar.qml index 34f25e73..b8400273 100644 --- a/components/TitleBar.qml +++ b/components/TitleBar.qml @@ -28,6 +28,7 @@ import QtQuick 2.2 import QtQuick.Window 2.0 +import QtQuick.Layouts 1.1 Rectangle { id: titleBar @@ -37,7 +38,7 @@ Rectangle { property alias basicButtonVisible: goToBasicVersionButton.visible property bool customDecorations: true signal goToBasicVersion(bool yes) - height: customDecorations ? 30 : 0 + height: customDecorations && !isMobile ? 30 : 0 y: -height property string title property alias maximizeButtonVisible: maximizeButton.visible @@ -53,21 +54,22 @@ Rectangle { } Rectangle { + id: goToBasicVersionButton property bool containsMouse: titleBar.mouseX >= x && titleBar.mouseX <= x + width property bool checked: false anchors.top: parent.top anchors.left: parent.left - color: basicMouseArea.containsMouse || !leftPanel.visible ? "#FFE00A" : "#000000" - height: 30 + color: "#FFE00A" + height: 30 * scaleRatio width: height visible: isMobile Image { + width: parent.width * 2/3; + height: width; anchors.centerIn: parent - rotation: !leftPanel.visible ? 180 : 0 - source: parent.customDecorations || !leftPanel.visible ? "../images/goToBasicVersionHovered.png" : - "../images/gotoBasicVersion.png" + source: "../images/menu.png" } MouseArea { @@ -75,6 +77,7 @@ Rectangle { hoverEnabled: true anchors.fill: parent onClicked: { + releaseFocus() parent.checked = !parent.checked titleBar.goToBasicVersion(leftPanel.visible) } diff --git a/filter.cpp b/filter.cpp index 9a2c9356..36f2c968 100644 --- a/filter.cpp +++ b/filter.cpp @@ -71,6 +71,12 @@ bool filter::eventFilter(QObject *obj, QEvent *ev) { m_tabPressed = false; QString sks; +#ifdef Q_OS_ANDROID + if(ke->key() == Qt::Key_Back) { + qDebug() << "Android back hit"; + sks = "android_back"; + } +#endif if(ke->key() == Qt::Key_Control) { sks = "Ctrl"; #ifdef Q_OS_MAC diff --git a/images/menu.png b/images/menu.png new file mode 100644 index 00000000..95378c69 Binary files /dev/null and b/images/menu.png differ diff --git a/ios_get_libwallet.api.sh b/ios_get_libwallet.api.sh index 3592829c..df07a98e 100755 --- a/ios_get_libwallet.api.sh +++ b/ios_get_libwallet.api.sh @@ -1,4 +1,12 @@ #!/bin/bash -e + +# 3 header files required by monero are missing from the IOS SDK. I copied them from iphoneSimulator SDK +# cd /Applications/XCode.app +# sudo cp ./Contents/Developer/Platforms/iPhoneSimulator.platform/Developer/SDKs/iPhoneSimulator.sdk/usr/include/sys/vmmeter.h ./Contents/Developer/Platforms/iPhoneOS.platform/Developer/SDKs/iPhoneOS.sdk/usr/include/sys/ +# sudo cp ./Contents/Developer/Platforms/iPhoneSimulator.platform/Developer/SDKs/iPhoneSimulator.sdk/usr/include/netinet/udp_var.h ./Contents/Developer/Platforms/iPhoneOS.platform/Developer/SDKs/iPhoneOS.sdk/usr/include/netinet/ +# sudo cp ./Contents/Developer/Platforms/iPhoneSimulator.platform/Developer/SDKs/iPhoneSimulator.sdk/usr/include/netinet/ip_var.h ./Contents/Developer/Platforms/iPhoneOS.platform/Developer/SDKs/iPhoneOS.sdk/usr/include/netinet/ + + if [ -z $BUILD_TYPE ]; then BUILD_TYPE=release fi @@ -18,17 +26,17 @@ if [ -z $OPENSSL_ROOT_DIR ]; then fi echo "Building IOS armv7" -rm -r monero/build +rm -r monero/build > /dev/null mkdir -p monero/build/release pushd monero/build/release cmake -D IOS=ON -D ARCH=armv7 -D BOOST_LIBRARYDIR=${BOOST_INCLUDEDIR} -D BOOST_INCLUDEDIR=${BOOST_INCLUDEDIR} -D OPENSSL_INCLUDE_DIR=${OPENSSL_INCLUDE_DIR} -D OPENSSL_ROOT_DIR=${OPENSSL_ROOT_DIR} -D CMAKE_BUILD_TYPE=debug -D STATIC=ON -D BUILD_GUI_DEPS=ON -D INSTALL_VENDORED_LIBUNBOUND=ON -D CMAKE_INSTALL_PREFIX="/Users/jacob/crypto/monero-core/monero" ../.. make -j4 && make install popd echo "Building IOS arm64" -rm -r monero/build +rm -r monero/build > /dev/null mkdir -p monero/build/release pushd monero/build/release -cmake -D IOS=ON -D ARCH=arm64 -D BOOST_LIBRARYDIR=${BOOST_INCLUDEDIR} -D BOOST_INCLUDEDIR=${BOOST_INCLUDEDIR} -D OPENSSL_INCLUDE_DIR=${OPENSSL_INCLUDE_DIR} -D OPENSSL_ROOT_DIR=${OPENSSL_ROOT_DIR} -D CMAKE_BUILD_TYPE=debug -D STATIC=ON -D BUILD_GUI_DEPS=ON -D INSTALL_VENDORED_LIBUNBOUND=ON -D CMAKE_INSTALL_PREFIX="/Users/jacob/crypto/monero-core/monero" ../.. +cmake -D IOS=ON -D ARCH=armv8-a -D BOOST_LIBRARYDIR=${BOOST_INCLUDEDIR} -D BOOST_INCLUDEDIR=${BOOST_INCLUDEDIR} -D OPENSSL_INCLUDE_DIR=${OPENSSL_INCLUDE_DIR} -D OPENSSL_ROOT_DIR=${OPENSSL_ROOT_DIR} -D CMAKE_BUILD_TYPE=debug -D STATIC=ON -D BUILD_GUI_DEPS=ON -D INSTALL_VENDORED_LIBUNBOUND=ON -D CMAKE_INSTALL_PREFIX="/Users/jacob/crypto/monero-core/monero" ../.. make -j4 && make install popd diff --git a/main.cpp b/main.cpp index 1a9a979d..da9686dd 100644 --- a/main.cpp +++ b/main.cpp @@ -32,6 +32,8 @@ #include #include #include +#include +#include #include "clipboardAdapter.h" #include "filter.h" #include "oscursor.h" @@ -168,6 +170,7 @@ int main(int argc, char *argv[]) bool isWindows = false; bool isIOS = false; bool isMac = false; + bool isAndroid = false; #ifdef Q_OS_WIN isWindows = true; QStringList moneroAccountsRootDir = QStandardPaths::standardLocations(QStandardPaths::DocumentsLocation); @@ -180,9 +183,42 @@ int main(int argc, char *argv[]) #ifdef Q_OS_MAC isMac = true; #endif +#ifdef Q_OS_ANDROID + isAndroid = true; +#endif engine.rootContext()->setContextProperty("isWindows", isWindows); engine.rootContext()->setContextProperty("isIOS", isIOS); + engine.rootContext()->setContextProperty("isAndroid", isAndroid); + + // screen settings + // Mobile is designed on 128dpi + qreal ref_dpi = 128; + QRect geo = QApplication::desktop()->availableGeometry(); + QRect rect = QGuiApplication::primaryScreen()->geometry(); + qreal height = qMax(rect.width(), rect.height()); + qreal width = qMin(rect.width(), rect.height()); + qreal dpi = QGuiApplication::primaryScreen()->logicalDotsPerInch(); + qreal physicalDpi = QGuiApplication::primaryScreen()->physicalDotsPerInch(); + qreal calculated_ratio = physicalDpi/ref_dpi; + + engine.rootContext()->setContextProperty("screenWidth", geo.width()); + engine.rootContext()->setContextProperty("screenHeight", geo.height()); +#ifdef Q_OS_ANDROID + engine.rootContext()->setContextProperty("scaleRatio", calculated_ratio); +#else + engine.rootContext()->setContextProperty("scaleRatio", 1); +#endif + + qDebug() << "available width: " << geo.width(); + qDebug() << "available height: " << geo.height(); + qDebug() << "devicePixelRatio: " << app.devicePixelRatio(); + qDebug() << "screen height: " << height; + qDebug() << "screen width: " << width; + qDebug() << "screen logical dpi: " << dpi; + qDebug() << "screen Physical dpi: " << physicalDpi; + qDebug() << "screen calculated ratio: " << calculated_ratio; + if (!moneroAccountsRootDir.empty()) { QString moneroAccountsDir = moneroAccountsRootDir.at(0) + "/Monero/wallets"; diff --git a/main.qml b/main.qml index 285423f3..c98e9efe 100644 --- a/main.qml +++ b/main.qml @@ -57,7 +57,7 @@ ApplicationWindow { property bool isNewWallet: false property int restoreHeight:0 property bool daemonSynced: false - property int maxWindowHeight: (Screen.height < 900)? 720 : 800; + property int maxWindowHeight: (isAndroid || isIOS)? screenHeight : (screenHeight < 900)? 720 : 800; property bool daemonRunning: false property alias toolTip: toolTip property string walletName @@ -66,8 +66,14 @@ ApplicationWindow { property int timeToUnlock: 0 property bool qrScannerEnabled: (typeof builtWithScanner != "undefined") && builtWithScanner property int blocksToSync: 1 - property var isMobile: (appWindow.width > 700) ? false : true + property var isMobile: (appWindow.width > 700 && !isAndroid) ? false : true property var cameraUi + property bool remoteNodeConnected: false + property bool androidCloseTapped: false; + // Default daemon addresses + readonly property string localDaemonAddress : !persistentSettings.testnet ? "localhost:18081" : "localhost:28081" + property string currentDaemonAddress; + property bool startLocalNodeCancelled: false // true if wallet ever synchronized property bool walletInitialized : false @@ -164,7 +170,6 @@ ApplicationWindow { function initialize() { console.log("initializing..") - walletInitialized = false; // Use stored log level if (persistentSettings.logLevel == 5) @@ -186,7 +191,7 @@ ApplicationWindow { console.log("Daemon change - closing " + currentWallet) closeWallet(); currentWallet = undefined - } else { + } else if (!walletInitialized) { // set page to transfer if not changing daemon middlePanel.state = "Transfer"; @@ -194,9 +199,13 @@ ApplicationWindow { } - walletManager.setDaemonAddress(persistentSettings.daemon_address) + + // Local daemon settings + walletManager.setDaemonAddress(localDaemonAddress) + + // wallet already opened with wizard, we just need to initialize it - if (typeof wizard.settings['wallet'] !== 'undefined') { + if (typeof wizard.m_wallet !== 'undefined') { console.log("using wizard wallet") //Set restoreHeight if(persistentSettings.restore_height > 0){ @@ -204,11 +213,11 @@ ApplicationWindow { restoreHeight = persistentSettings.restore_height } - connectWallet(wizard.settings['wallet']) + connectWallet(wizard.m_wallet) isNewWallet = true // We don't need the wizard wallet any more - delete to avoid conflict with daemon adress change - delete wizard.settings['wallet'] + delete wizard.m_wallet } else { var wallet_path = walletPath(); if(isIOS) @@ -236,12 +245,10 @@ ApplicationWindow { middlePanel.sweepUnmixableClicked.disconnect(handleSweepUnmixable); middlePanel.checkPaymentClicked.disconnect(handleCheckPayment); } + currentWallet = undefined; - if (isIOS) { - console.log("closing sync - ios") - walletManager.closeWallet(); - } else - walletManager.closeWalletAsync(); + walletManager.closeWallet(); + } function connectWallet(wallet) { @@ -270,14 +277,20 @@ ApplicationWindow { middlePanel.sweepUnmixableClicked.connect(handleSweepUnmixable); middlePanel.checkPaymentClicked.connect(handleCheckPayment); - console.log("initializing with daemon address: ", persistentSettings.daemon_address) + console.log("Recovering from seed: ", persistentSettings.is_recovering) console.log("restore Height", persistentSettings.restore_height) // Use saved daemon rpc login settings - currentWallet.setDaemonLogin(persistentSettings.daemonUsername, persistentSettings.daemonPassword); + currentWallet.setDaemonLogin(persistentSettings.daemonUsername, persistentSettings.daemonPassword) - currentWallet.initAsync(persistentSettings.daemon_address, 0, persistentSettings.is_recovering, persistentSettings.restore_height); + if(persistentSettings.useRemoteNode) + currentDaemonAddress = persistentSettings.remoteNodeAddress + else + currentDaemonAddress = localDaemonAddress + + console.log("initializing with daemon address: ", currentDaemonAddress) + currentWallet.initAsync(currentDaemonAddress, 0, persistentSettings.is_recovering, persistentSettings.restore_height); } function walletPath() { @@ -302,7 +315,7 @@ ApplicationWindow { middlePanel.transferView.updatePriorityDropdown(); // If wallet isnt connected and no daemon is running - Ask - if(isDaemonLocal() && !walletInitialized && status === Wallet.ConnectionStatus_Disconnected && !daemonManager.running(persistentSettings.testnet)){ + if(!isMobile && isDaemonLocal() && !walletInitialized && status === Wallet.ConnectionStatus_Disconnected && !daemonManager.running(persistentSettings.testnet)){ daemonManagerDialog.open(); } // initialize transaction history once wallet is initialized first time; @@ -358,10 +371,25 @@ ApplicationWindow { console.log("New block found - updating history") currentWallet.history.refresh() timeToUnlock = currentWallet.history.minutesToUnlock - leftPanel.minutesToUnlockTxt = (timeToUnlock > 0)? (timeToUnlock == 20)? qsTr("Unlocked balance (waiting for block)").arg(timeToUnlock) : qsTr("Unlocked balance (~%1 min)").arg(timeToUnlock) : qsTr("Unlocked balance"); + leftPanel.minutesToUnlockTxt = (timeToUnlock > 0)? (timeToUnlock == 20)? qsTr("Unlocked balance (waiting for block)") : qsTr("Unlocked balance (~%1 min)").arg(timeToUnlock) : qsTr("Unlocked balance"); } } + function connectRemoteNode() { + console.log("connecting remote node"); + persistentSettings.useRemoteNode = true; + currentWallet.initAsync(persistentSettings.remoteNodeAddress); + remoteNodeConnected = true; + } + + function disconnectRemoteNode() { + console.log("disconnecting remote node"); + persistentSettings.useRemoteNode = false; + currentDaemonAddress = localDaemonAddress + currentWallet.initAsync(currentDaemonAddress); + remoteNodeConnected = false; + } + function onWalletRefresh() { console.log(">>> wallet refreshed") @@ -383,6 +411,31 @@ ApplicationWindow { // Update transfer page status middlePanel.updateStatus(); + // Use remote node while local daemon is syncing + if (persistentSettings.useRemoteNode) { + var localNodeConnected = walletManager.connected; + var localNodeSynced = localNodeConnected && walletManager.localDaemonSynced() + if (!currentWallet.connected() || !localNodeSynced) { + console.log("Using remote node while local node is syncing") + // Connect to remote node if not already connected + if(!remoteNodeConnected) { + connectRemoteNode(); + } + + //update local daemon sync progress bar + if(localNodeConnected) { + leftPanel.progressBar.updateProgress(walletManager.blockchainHeight(),walletManager.blockchainTargetHeight(), 0, qsTr("Remaining blocks (local node):")); + leftPanel.progressBar.visible = true + } else if (persistentSettings.startLocalNode && !startLocalNodeCancelled) { + daemonManagerDialog.open() + } + + // local daemon is synced - use it! + } else if (localNodeSynced && remoteNodeConnected) { + disconnectRemoteNode(); + } + } + // Refresh is succesfull if blockchain height > 1 if (currentWallet.blockChainHeight() > 1){ @@ -392,9 +445,6 @@ ApplicationWindow { console.log("Saving wallet after first refresh"); currentWallet.store() isNewWallet = false - - // Update History - currentWallet.history.refresh(); } // recovering from seed is finished after first refresh @@ -403,6 +453,10 @@ ApplicationWindow { } } + // Update history on every refresh if it's empty + if(currentWallet.history.count == 0) + currentWallet.history.refresh() + onWalletUpdate(); } @@ -462,7 +516,8 @@ ApplicationWindow { function onWalletMoneyReceived(txId, amount) { // refresh transaction history here currentWallet.refresh() - currentWallet.history.refresh() // this will refresh model + console.log("Confirmed money found") + // history refresh is handled by walletUpdated } function onWalletUnconfirmedMoneyReceived(txId, amount) { @@ -473,6 +528,7 @@ ApplicationWindow { function onWalletMoneySent(txId, amount) { // refresh transaction history here + console.log("money sent found") currentWallet.refresh() currentWallet.history.refresh() // this will refresh model } @@ -706,7 +762,7 @@ ApplicationWindow { ", txid: ", txid, ", txkey: ", txkey); - var result = walletManager.checkPayment(address, txid, txkey, persistentSettings.daemon_address); + var result = walletManager.checkPayment(address, txid, txkey, currentDaemonAddress); var results = result.split("|"); if (results.length < 4) { informationPopup.title = qsTr("Error") + translationManager.emptyString; @@ -797,7 +853,7 @@ ApplicationWindow { objectName: "appWindow" visible: true -// width: Screen.width //rightPanelExpanded ? 1269 : 1269 - 300 +// width: screenWidth //rightPanelExpanded ? 1269 : 1269 - 300 // height: 900 //300//maxWindowHeight; color: "#FFFFFF" flags: persistentSettings.customDecorations ? (Qt.FramelessWindowHint | Qt.WindowSystemMenuHint | Qt.Window | Qt.WindowMinimizeButtonHint) : (Qt.WindowSystemMenuHint | Qt.Window | Qt.WindowMinimizeButtonHint | Qt.WindowCloseButtonHint | Qt.WindowTitleHint | Qt.WindowMaximizeButtonHint) @@ -892,6 +948,9 @@ ApplicationWindow { property string daemonPassword: "" property bool transferShowAdvanced: false property string blockchainDataDir: "" + property bool startLocalNode: true + property bool useRemoteNode: false + property string remoteNodeAddress: "" } // Information dialog @@ -899,6 +958,8 @@ ApplicationWindow { // dynamically change onclose handler property var onCloseCallback id: informationPopup + anchors.fill: parent + z: parent.z + 1 cancelVisible: false onAccepted: { if (onCloseCallback) { @@ -909,6 +970,7 @@ ApplicationWindow { // Confrirmation aka question dialog StandardDialog { + z: parent.z + 1 id: transactionConfirmationPopup onAccepted: { close(); @@ -935,7 +997,9 @@ ApplicationWindow { } StandardDialog { + z: parent.z + 1 id: confirmationDialog + anchors.fill: parent property var onAcceptedCallback property var onRejectedCallback onAccepted: { @@ -955,9 +1019,17 @@ ApplicationWindow { title: "Please choose a file" folder: "file://" +moneroAccountsDir nameFilters: [ "Wallet files (*.keys)"] + sidebarVisible: false + onAccepted: { persistentSettings.wallet_path = walletManager.urlToLocalPath(fileDialog.fileUrl) + if(isIOS) + persistentSettings.wallet_path = persistentSettings.wallet_path.replace(moneroAccountsDir,"") + console.log("ÖPPPPNA") + console.log(moneroAccountsDir) + console.log(fileDialog.fileUrl) + console.log(persistentSettings.wallet_path) initialize(); } onRejected: { @@ -967,9 +1039,63 @@ ApplicationWindow { } + // Choose blockchain folder + FileDialog { + id: blockchainFileDialog + title: "Please choose a folder" + selectFolder: true + folder: "file://" + persistentSettings.blockchainDataDir + + onAccepted: { + var dataDir = walletManager.urlToLocalPath(blockchainFileDialog.fileUrl) + var validator = daemonManager.validateDataDir(dataDir); + if(!validator.valid) { + + confirmationDialog.title = qsTr("Warning") + translationManager.emptyString; + confirmationDialog.text = ""; + if(validator.readOnly) + confirmationDialog.text += qsTr("Error: Filesystem is read only") + "\n\n" + if(validator.storageAvailable < 20) + confirmationDialog.text += qsTr("Warning: There's only %1 GB available on the device. Blockchain requires ~%30 GB of data.").arg(validator.storageAvailable).arg(15) + "\n\n" + else + confirmationDialog.text += qsTr("Note: There's %1 GB available on the device. Blockchain requires ~%30 GB of data.").arg(validator.storageAvailable).arg(15) + "\n\n" + if(!validator.lmdbExists) + confirmationDialog.text += qsTr("Note: lmdb folder not found. A new folder will be created.") + "\n\n" + + + confirmationDialog.icon = StandardIcon.Question + confirmationDialog.cancelText = qsTr("Cancel") + + // Continue + confirmationDialog.onAcceptedCallback = function() { + persistentSettings.blockchainDataDir = dataDir + } + + // Cancel + confirmationDialog.onRejectedCallback = function() { + }; + + confirmationDialog.open() + } else { + persistentSettings.blockchainDataDir = dataDir + } + + delete validator; + + + } + onRejected: { + console.log("data dir selection canceled") + } + + } + + PasswordDialog { id: passwordDialog - + visible: false + z: parent.z + 1 + anchors.fill: parent onAccepted: { appWindow.initialize(); } @@ -988,11 +1114,43 @@ ApplicationWindow { onAcceptedCallback(); } } + + PasswordDialog { + id: settingsPasswordDialog + z: parent.z + 1 + visible:false + anchors.fill: parent + onAccepted: { + if(appWindow.password === settingsPasswordDialog.password){ + if(currentWallet.seedLanguage == "") { + console.log("No seed language set. Using English as default"); + currentWallet.setSeedLanguage("English"); + } + + // Load keys page + middlePanel.state = "Keys" + + } else { + informationPopup.title = qsTr("Error") + translationManager.emptyString; + informationPopup.text = qsTr("Wrong password"); + informationPopup.open() + informationPopup.onCloseCallback = function() { + settingsPasswordDialog.open() + } + } + + settingsPasswordDialog.password = "" + } + onRejected: { + appWindow.showPageRequest("Settings"); + } + } DaemonManagerDialog { id: daemonManagerDialog onRejected: { loadPage("Settings"); + startLocalNodeCancelled = true } } @@ -1001,8 +1159,8 @@ ApplicationWindow { id: splash width: appWindow.width / 1.5 height: appWindow.height / 2 - x: (appWindow.width - width) / 2 + appWindow.x - y: (appWindow.height - height) / 2 + appWindow.y + x: (appWindow.width - width) / 2 + y: (appWindow.height - height) / 2 messageText: qsTr("Please wait...") } @@ -1020,14 +1178,15 @@ ApplicationWindow { PropertyChanges { target: middlePanel; visible: false } PropertyChanges { target: titleBar; basicButtonVisible: false } PropertyChanges { target: wizard; visible: true } - PropertyChanges { target: appWindow; width: (Screen.width < 930)? Screen.width : 930; } + PropertyChanges { target: appWindow; width: (screenWidth < 930 || isAndroid || isIOS)? screenWidth : 930; } PropertyChanges { target: appWindow; height: maxWindowHeight; } - PropertyChanges { target: resizeArea; visible: false } + PropertyChanges { target: resizeArea; visible: true } PropertyChanges { target: titleBar; maximizeButtonVisible: false } // PropertyChanges { target: frameArea; blocked: true } PropertyChanges { target: titleBar; visible: false } PropertyChanges { target: titleBar; y: 0 } PropertyChanges { target: titleBar; title: qsTr("Program setup wizard") + translationManager.emptyString } + PropertyChanges { target: mobileHeader; visible: false } }, State { name: "normal" PropertyChanges { target: leftPanel; visible: (isMobile)? false : true } @@ -1035,7 +1194,7 @@ ApplicationWindow { PropertyChanges { target: middlePanel; visible: true } PropertyChanges { target: titleBar; basicButtonVisible: true } PropertyChanges { target: wizard; visible: false } - PropertyChanges { target: appWindow; width: (Screen.width < 969)? Screen.width : 969 } //rightPanelExpanded ? 1269 : 1269 - 300; + PropertyChanges { target: appWindow; width: (screenWidth < 969 || isAndroid || isIOS)? screenWidth : 969 } //rightPanelExpanded ? 1269 : 1269 - 300; PropertyChanges { target: appWindow; height: maxWindowHeight; } PropertyChanges { target: resizeArea; visible: true } PropertyChanges { target: titleBar; maximizeButtonVisible: true } @@ -1043,6 +1202,7 @@ ApplicationWindow { PropertyChanges { target: titleBar; visible: true } // PropertyChanges { target: titleBar; y: 0 } PropertyChanges { target: titleBar; title: qsTr("Monero") + translationManager.emptyString } + PropertyChanges { target: mobileHeader; visible: isMobile ? true : false } } ] @@ -1051,7 +1211,7 @@ ApplicationWindow { visible: isMobile anchors.left: parent.left anchors.right: parent.right - height: visible? 65 : 0 + height: visible? 65 * scaleRatio : 0 } LeftPanel { @@ -1068,6 +1228,7 @@ ApplicationWindow { onMiningClicked: {middlePanel.state = "Mining"; if(isMobile) hideMenu()} onSignClicked: {middlePanel.state = "Sign"; if(isMobile) hideMenu()} onSettingsClicked: {middlePanel.state = "Settings"; if(isMobile) hideMenu()} + onKeysClicked: {settingsPasswordDialog.open(); if(isMobile) hideMenu()} } RightPanel { @@ -1094,22 +1255,6 @@ ApplicationWindow { visible: false } -// MouseArea { -// id: frameArea -// property bool blocked: false -// anchors.top: parent.top -// anchors.left: parent.left -// anchors.right: parent.right -// height: 30 -// z: 1 -// hoverEnabled: true -// propagateComposedEvents: true -// onPressed: mouse.accepted = false -// onReleased: mouse.accepted = false -// onMouseXChanged: titleBar.mouseX = mouseX -// onContainsMouseChanged: titleBar.containsMouse = containsMouse -// } - SequentialAnimation { id: goToBasicAnimation // PropertyAction { @@ -1324,7 +1469,7 @@ ApplicationWindow { y: 6 lineHeight: 0.7 font.family: "Arial" - font.pixelSize: 12 + font.pixelSize: 12 * scaleRatio color: "#FFFFFF" } } @@ -1335,11 +1480,70 @@ ApplicationWindow { } } + // TODO: Make the callback dynamic + Timer { + id: statusMessageTimer + interval: 5; + running: false; + repeat: false + onTriggered: resetAndroidClose() + triggeredOnStart: false + } + + Rectangle { + id: statusMessage + z: 99 + visible: false + property alias text: statusMessageText.text + anchors.bottom: parent.bottom + width: statusMessageText.contentWidth + 20 * scaleRatio + anchors.horizontalCenter: parent.horizontalCenter + color: "black" + height: 40 * scaleRatio + Text { + id: statusMessageText + anchors.fill: parent + anchors.margins: 10 * scaleRatio + font.pixelSize: 14 * scaleRatio + color: "white" + } + } + + function resetAndroidClose() { + console.log("resetting android close"); + androidCloseTapped = false; + statusMessage.visible = false + } + + function showStatusMessage(msg,timeout) { + console.log("showing status message") + statusMessageTimer.interval = timeout * 1000; + statusMessageTimer.start() + statusMessageText.text = msg; + statusMessage.visible = true + } + onClosing: { + close.accepted = false; + console.log("blocking close event"); + if(isAndroid) { + console.log("blocking android exit"); + if(qrScannerEnabled) + cameraUi.state = "Stopped" + + if(!androidCloseTapped) { + androidCloseTapped = true; + appWindow.showStatusMessage(qsTr("Tap again to close..."),3) + + // first close + return; + } + + + } // If daemon is running - prompt user before exiting if(typeof daemonManager != "undefined" && daemonManager.running(persistentSettings.testnet)) { - close.accepted = false; // Show confirmation dialog confirmationDialog.title = qsTr("Daemon is running") + translationManager.emptyString; @@ -1363,6 +1567,7 @@ ApplicationWindow { } function closeAccepted(){ + console.log("close accepted"); // Close wallet non async on exit daemonManager.exit(); walletManager.closeWallet(); @@ -1407,4 +1612,14 @@ ApplicationWindow { return false } + function releaseFocus() { + // Workaround to release focus from textfield when scrolling (https://bugreports.qt.io/browse/QTBUG-34867) + if(isAndroid) { + console.log("releasing focus") + middlePanel.focus = true + middlePanel.focus = false + } + + + } } diff --git a/monero-wallet-gui.pro b/monero-wallet-gui.pro index b6383d2b..3bb14914 100644 --- a/monero-wallet-gui.pro +++ b/monero-wallet-gui.pro @@ -86,7 +86,7 @@ ios:arm64 { LIBS += \ -L$$PWD/../ofxiOSBoost/build/libs/boost/lib/arm64 \ } -!ios { +!ios:!android { LIBS += -L$$WALLET_ROOT/lib \ -lwallet_merged \ -lepee \ @@ -95,6 +95,17 @@ LIBS += -L$$WALLET_ROOT/lib \ -lreadline \ } +android { + message("Host is Android") + LIBS += -L$$WALLET_ROOT/lib \ + -lwallet_merged \ + -lepee \ + -lunbound \ + -leasylogging +} + + + ios { message("Host is IOS") @@ -105,7 +116,8 @@ ios { LIBS += -L$$WALLET_ROOT/lib-ios \ -lwallet_merged \ -lepee \ - -lunbound + -lunbound \ + -leasylogging LIBS+= \ -L$$PWD/../OpenSSL-for-iPhone/lib \ diff --git a/pages/AddressBook.qml b/pages/AddressBook.qml index 65f097c0..7904c594 100644 --- a/pages/AddressBook.qml +++ b/pages/AddressBook.qml @@ -33,167 +33,106 @@ import moneroComponents.AddressBook 1.0 import moneroComponents.AddressBookModel 1.0 Rectangle { - color: "#F0EEEE" id: root + color: "#F0EEEE" property var model - Text { - id: newEntryText + ColumnLayout { + anchors.margins: 17 * scaleRatio anchors.left: parent.left - anchors.right: parent.right anchors.top: parent.top - anchors.leftMargin: 17 - anchors.topMargin: 17 + anchors.right: parent.right + spacing: 10 * scaleRatio - elide: Text.ElideRight - font.family: "Arial" - font.pixelSize: 18 - color: "#4A4949" - text: qsTr("Add new entry") + translationManager.emptyString - } - - Label { - id: addressLabel - anchors.left: parent.left - anchors.top: newEntryText.bottom - anchors.leftMargin: 17 - anchors.topMargin: 17 - text: qsTr("Address") + translationManager.emptyString - fontSize: 14 - } - - StandardButton { - id: qrfinderButton - anchors.left: parent.left - anchors.leftMargin: 17 - anchors.topMargin: 5 - anchors.top: addressLabel.bottom - text: qsTr("QRCODE") + translationManager.emptyString - shadowReleasedColor: "#FF4304" - shadowPressedColor: "#B32D00" - releasedColor: "#FF6C3C" - pressedColor: "#FF4304" - visible : appWindow.qrScannerEnabled - enabled : visible - width: visible ? 60 : 0 - onClicked: { - cameraUi.state = "Capture" - cameraUi.qrcode_decoded.connect(updateFromQrCode) + Label { + id: addressLabel + anchors.left: parent.left + text: qsTr("Address") + translationManager.emptyString } - } - LineEdit { - id: addressLine - anchors.left: qrfinderButton.right - anchors.right: parent.right - anchors.top: addressLabel.bottom - anchors.rightMargin: 17 - anchors.topMargin: 5 - error: true; - placeholderText: qsTr("4...") + translationManager.emptyString - } + RowLayout { + StandardButton { + id: qrfinderButton + text: qsTr("Qr Code") + translationManager.emptyString + shadowReleasedColor: "#FF4304" + shadowPressedColor: "#B32D00" + releasedColor: "#FF6C3C" + pressedColor: "#FF4304" + visible : appWindow.qrScannerEnabled + enabled : visible + width: visible ? 60 * scaleRatio : 0 + onClicked: { + cameraUi.state = "Capture" + cameraUi.qrcode_decoded.connect(updateFromQrCode) + } + } - Label { - id: paymentIdLabel - anchors.left: parent.left - anchors.top: addressLine.bottom - anchors.leftMargin: 17 - anchors.topMargin: 17 - text: qsTr("Payment ID (Optional)") + translationManager.emptyString - fontSize: 14 - } + LineEdit { + Layout.fillWidth: true; + id: addressLine + error: true; + placeholderText: qsTr("4...") + translationManager.emptyString + } + } - LineEdit { - id: paymentIdLine - anchors.left: parent.left - anchors.right: parent.right - anchors.top: paymentIdLabel.bottom - anchors.leftMargin: 17 - anchors.rightMargin: 17 - anchors.topMargin: 5 - placeholderText: qsTr("Paste 64 hexadecimal characters") + translationManager.emptyString - } + Label { + id: paymentIdLabel + text: qsTr("Payment ID (Optional)") + translationManager.emptyString + tipText: qsTr("Payment ID

A unique user name used in
the address book. It is not a
transfer of information sent
during the transfer") + + translationManager.emptyString + } - Label { - id: descriptionLabel - anchors.left: parent.left - anchors.top: paymentIdLine.bottom - anchors.leftMargin: 17 - anchors.topMargin: 17 - text: qsTr("Description (Optional)") + translationManager.emptyString - fontSize: 14 - } + LineEdit { + id: paymentIdLine + Layout.fillWidth: true; + placeholderText: qsTr("Paste 64 hexadecimal characters") + translationManager.emptyString + } - LineEdit { - id: descriptionLine - anchors.left: parent.left - anchors.right: parent.right - anchors.top: descriptionLabel.bottom - anchors.leftMargin: 17 - anchors.rightMargin: 17 - anchors.topMargin: 5 - placeholderText: qsTr("Give this entry a name or description") + translationManager.emptyString - } + Label { + id: descriptionLabel + text: qsTr("Description (Optional)") + translationManager.emptyString + } + + LineEdit { + id: descriptionLine + Layout.fillWidth: true; + placeholderText: qsTr("Give this entry a name or description") + translationManager.emptyString + } - RowLayout { - id: addButton - anchors.left: parent.left - anchors.top: descriptionLine.bottom - anchors.leftMargin: 17 - anchors.topMargin: 17 + RowLayout { + id: addButton + Layout.bottomMargin: 17 * scaleRatio + StandardButton { + shadowReleasedColor: "#FF4304" + shadowPressedColor: "#B32D00" + releasedColor: "#FF6C3C" + pressedColor: "#FF4304" + text: qsTr("Add") + translationManager.emptyString + enabled: checkInformation(addressLine.text, paymentIdLine.text, appWindow.persistentSettings.testnet) - StandardButton { - shadowReleasedColor: "#FF4304" - shadowPressedColor: "#B32D00" - releasedColor: "#FF6C3C" - pressedColor: "#FF4304" - text: qsTr("Add") + translationManager.emptyString - enabled: checkInformation(addressLine.text, paymentIdLine.text, appWindow.persistentSettings.testnet) + onClicked: { + if (!currentWallet.addressBook.addRow(addressLine.text.trim(), paymentIdLine.text.trim(), descriptionLine.text)) { + informationPopup.title = qsTr("Error") + translationManager.emptyString; + // TODO: check currentWallet.addressBook.errorString() instead. + if(currentWallet.addressBook.errorCode() === AddressBook.Invalid_Address) + informationPopup.text = qsTr("Invalid address") + translationManager.emptyString + else if(currentWallet.addressBook.errorCode() === AddressBook.Invalid_Payment_Id) + informationPopup.text = currentWallet.addressBook.errorString() + else + informationPopup.text = qsTr("Can't create entry") + translationManager.emptyString - onClicked: { - if (!currentWallet.addressBook.addRow(addressLine.text.trim(), paymentIdLine.text.trim(), descriptionLine.text)) { - informationPopup.title = qsTr("Error") + translationManager.emptyString; - // TODO: check currentWallet.addressBook.errorString() instead. - if(currentWallet.addressBook.errorCode() === AddressBook.Invalid_Address) - informationPopup.text = qsTr("Invalid address") + translationManager.emptyString - else if(currentWallet.addressBook.errorCode() === AddressBook.Invalid_Payment_Id) - informationPopup.text = currentWallet.addressBook.errorString() - else - informationPopup.text = qsTr("Can't create entry") + translationManager.emptyString - - informationPopup.onCloseCallback = null - informationPopup.open(); - } else { - addressLine.text = ""; - paymentIdLine.text = ""; - descriptionLine.text = ""; + informationPopup.onCloseCallback = null + informationPopup.open(); + } else { + addressLine.text = ""; + paymentIdLine.text = ""; + descriptionLine.text = ""; + } } } } - } - - - Item { - id: expandItem - property bool expanded: false - - anchors.right: parent.right - anchors.bottom: tableRect.top - width: 34 - height: 34 - - Image { - anchors.centerIn: parent - source: "../images/expandTable.png" - rotation: parent.expanded ? 180 : 0 - } - - MouseArea { - anchors.fill: parent - onClicked: parent.expanded = !parent.expanded - } } Rectangle { @@ -201,8 +140,7 @@ Rectangle { anchors.left: parent.left anchors.right: parent.right anchors.bottom: parent.bottom - height: expandItem.expanded ? parent.height - newEntryText.y - newEntryText.height - 17 : - parent.height - addButton.y - addButton.height - 17 + height: parent.height - addButton.y - addButton.height - 36 * scaleRatio color: "#FFFFFF" Behavior on height { @@ -217,44 +155,6 @@ Rectangle { color: "#DBDBDB" } - ListModel { - id: columnsModel -// ListElement { columnName: qsTr("Address") + translationManager.emptyString; columnWidth: 148 } -// ListElement { columnName: qsTr("Payment ID") + translationManager.emptyString; columnWidth: 148 } -// ListElement { columnName: qsTr("Description") + translationManager.emptyString; columnWidth: 148 } -// - } - - TableHeader { - id: header - anchors.left: parent.left - anchors.right: parent.right - anchors.top: parent.top - anchors.topMargin: 17 - anchors.leftMargin: 14 - anchors.rightMargin: 14 - dataModel: columnsModel - onSortRequest: console.log("column: " + column + " desc: " + desc) - } - - ListModel { - id: testModel - ListElement { paymentId: "faef56b9acf67a7dba75ec01f403497049d7cff111628edfe7b57278554dc798"; address: "faef56b9acf67a7dba75ec01f403497049d7cff111628edfe7b57278554dc798"; description: "Client from Australia" } - ListElement { paymentId: "faef56b9acf67a7dba75ec01f403497049d7cff111628edfe7b57278554dc798"; address: "faef56b9acf67a7dba75ec01f403497049d7cff111628edfe7b57278554dc798"; description: "Client from Australia" } - ListElement { paymentId: "faef56b9acf67a7dba75ec01f403497049d7cff111628edfe7b57278554dc798"; address: "faef56b9acf67a7dba75ec01f403497049d7cff111628edfe7b57278554dc798"; description: "Client from Australia" } - ListElement { paymentId: "faef56b9acf67a7dba75ec01f403497049d7cff111628edfe7b57278554dc798"; address: "faef56b9acf67a7dba75ec01f403497049d7cff111628edfe7b57278554dc798"; description: "Client from Australia" } - ListElement { paymentId: ""; address: "faef56b9acf67a7dba75ec01f403497049d7cff111628edfe7b57278554dc798"; description: "" } - ListElement { paymentId: "faef56b9acf67a7dba75ec01f403497049d7cff111628edfe7b57278554dc798"; address: "faef56b9acf67a7dba75ec01f403497049d7cff111628edfe7b57278554dc798"; description: "Client from Australia" } - ListElement { paymentId: "faef56b9acf67a7dba75ec01f403497049d7cff111628edfe7b57278554dc798"; address: "faef56b9acf67a7dba75ec01f403497049d7cff111628edfe7b57278554dc798"; description: "Client from Australia" } - ListElement { paymentId: "faef56b9acf67a7dba75ec01f403497049d7cff111628edfe7b57278554dc798"; address: "faef56b9acf67a7dba75ec01f403497049d7cff111628edfe7b57278554dc798"; description: "Client from Australia" } - ListElement { paymentId: "faef56b9acf67a7dba75ec01f403497049d7cff111628edfe7b57278554dc798"; address: "faef56b9acf67a7dba75ec01f403497049d7cff111628edfe7b57278554dc798"; description: "Client from Australia" } - ListElement { paymentId: "faef56b9acf67a7dba75ec01f403497049d7cff111628edfe7b57278554dc798"; address: "faef56b9acf67a7dba75ec01f403497049d7cff111628edfe7b57278554dc798"; description: "Client from Australia" } - ListElement { paymentId: "faef56b9acf67a7dba75ec01f403497049d7cff111628edfe7b57278554dc798"; address: "faef56b9acf67a7dba75ec01f403497049d7cff111628edfe7b57278554dc798"; description: "Client from Australia" } - ListElement { paymentId: "faef56b9acf67a7dba75ec01f403497049d7cff111628edfe7b57278554dc798"; address: "faef56b9acf67a7dba75ec01f403497049d7cff111628edfe7b57278554dc798"; description: "Client from Australia" } - ListElement { paymentId: "faef56b9acf67a7dba75ec01f403497049d7cff111628edfe7b57278554dc798"; address: "faef56b9acf67a7dba75ec01f403497049d7cff111628edfe7b57278554dc798"; description: "Client from Australia" } - ListElement { paymentId: ""; address: "faef56b9acf67a7dba75ec01f403497049d7cff111628edfe7b57278554dc798"; description: "" } - } - Scroll { id: flickableScroll anchors.right: table.right @@ -268,7 +168,7 @@ Rectangle { id: table anchors.left: parent.left anchors.right: parent.right - anchors.top: header.bottom + anchors.top: parent.top anchors.bottom: parent.bottom anchors.leftMargin: 14 anchors.rightMargin: 14 diff --git a/pages/History.qml b/pages/History.qml index cd8b03d8..9b184741 100644 --- a/pages/History.qml +++ b/pages/History.qml @@ -80,7 +80,7 @@ Rectangle { } onModelChanged: { - if (typeof model !== 'undefined') { + if (typeof model !== 'undefined' && model != null) { selectedAmount.text = getSelectedAmount() @@ -146,30 +146,6 @@ Rectangle { fontSize: 14 } - - // Filter by Address input (senseless, removing) - /* - Label { - id: addressLabel - anchors.left: parent.left - anchors.top: filterHeaderText.bottom - anchors.leftMargin: 17 - anchors.topMargin: 17 - text: qsTr("Address") - fontSize: 14 - } - - LineEdit { - id: addressLine - anchors.left: parent.left - anchors.right: parent.right - anchors.top: addressLabel.bottom - anchors.leftMargin: 17 - anchors.rightMargin: 17 - anchors.topMargin: 5 - } - */ - // Filter by string LineEdit { visible: !isMobile @@ -488,7 +464,7 @@ Rectangle { ListModel { id: columnsModel - + property int pidWidth: 127 * scaleRatio ListElement { columnName: "Payment ID"; columnWidth: 127 } ListElement { columnName: "Date"; columnWidth: 100 } ListElement { columnName: "Block height"; columnWidth: 150 } @@ -498,12 +474,13 @@ Rectangle { TableHeader { id: header + visible: !isMobile anchors.left: parent.left anchors.right: parent.right anchors.top: parent.top - anchors.topMargin: 17 - anchors.leftMargin: 14 - anchors.rightMargin: 14 + anchors.topMargin: 17 * scaleRatio + anchors.leftMargin: 14 * scaleRatio + anchors.rightMargin: 14 * scaleRatio dataModel: columnsModel offset: 20 onSortRequest: { @@ -532,8 +509,9 @@ Rectangle { Scroll { id: flickableScroll + visible: !isMobile anchors.right: table.right - anchors.rightMargin: -14 + anchors.rightMargin: !isMobile ? -14 * scaleRatio : 0 anchors.top: table.top anchors.bottom: table.bottom flickable: table @@ -541,19 +519,37 @@ Rectangle { HistoryTable { id: table + visible: !isMobile anchors.left: parent.left anchors.right: parent.right anchors.top: header.bottom anchors.bottom: parent.bottom - anchors.leftMargin: 14 - anchors.rightMargin: 14 + anchors.leftMargin: 14 * scaleRatio + anchors.rightMargin: 14 * scaleRatio onContentYChanged: flickableScroll.flickableContentYChanged() - model: root.model + model: !isMobile ? root.model : null + addressBookModel: null + } + + HistoryTableMobile { + id: tableMobile + visible: isMobile + anchors.left: parent.left + anchors.right: parent.right + anchors.top: parent.top + anchors.bottom: parent.bottom + onContentYChanged: flickableScroll.flickableContentYChanged() + model: isMobile ? root.model : null addressBookModel: null } } function onPageCompleted() { - table.addressBookModel = appWindow.currentWallet ? appWindow.currentWallet.addressBookModel : null + if(currentWallet != null && typeof currentWallet.history !== "undefined" ) { + currentWallet.history.refresh() + table.addressBookModel = currentWallet ? currentWallet.addressBookModel : null + transactionTypeDropdown.update() + } + } } diff --git a/pages/Keys.qml b/pages/Keys.qml new file mode 100644 index 00000000..3fad708d --- /dev/null +++ b/pages/Keys.qml @@ -0,0 +1,221 @@ +// Copyright (c) 2014-2017, The Monero Project +// +// All rights reserved. +// +// Redistribution and use in source and binary forms, with or without modification, are +// permitted provided that the following conditions are met: +// +// 1. Redistributions of source code must retain the above copyright notice, this list of +// conditions and the following disclaimer. +// +// 2. Redistributions in binary form must reproduce the above copyright notice, this list +// of conditions and the following disclaimer in the documentation and/or other +// materials provided with the distribution. +// +// 3. Neither the name of the copyright holder nor the names of its contributors may be +// used to endorse or promote products derived from this software without specific +// prior written permission. +// +// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY +// EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF +// MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL +// THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, +// PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS +// INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, +// STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF +// THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + +import QtQuick 2.0 +import QtQuick.Controls 1.4 +import QtQuick.Controls.Styles 1.4 +import QtQuick.Layouts 1.1 +import QtQuick.Dialogs 1.2 +import "../version.js" as Version + + +import "../components" +import moneroComponents.Clipboard 1.0 + +Rectangle { + property bool viewOnly: false + id: page + + color: "#F0EEEE" + + Clipboard { id: clipboard } + + ColumnLayout { + id: mainLayout + anchors.margins: 17 * scaleRatio + anchors.left: parent.left + anchors.top: parent.top + anchors.right: parent.right + spacing: 20 * scaleRatio + Layout.fillWidth: true + + //! Manage wallet + ColumnLayout { + Layout.fillWidth: true + Label { + Layout.fillWidth: true + text: qsTr("Mnemonic seed") + translationManager.emptyString + } + Rectangle { + Layout.fillWidth: true + height: 1 + color: "#DEDEDE" + } + TextEdit { + id: seedText + wrapMode: TextEdit.Wrap + Layout.fillWidth: true; + font.pixelSize: 14 * scaleRatio + readOnly: true + MouseArea { + anchors.fill: parent + onClicked: { + appWindow.showStatusMessage(qsTr("Double tap to copy"),3) + } + onDoubleClicked: { + parent.selectAll() + parent.copy() + parent.deselect() + console.log("copied to clipboard"); + appWindow.showStatusMessage(qsTr("Seed copied to clipboard"),3) + } + } + } + } + + ColumnLayout { + Layout.fillWidth: true + Label { + Layout.fillWidth: true + text: qsTr("Keys") + translationManager.emptyString + } + Rectangle { + Layout.fillWidth: true + height: 1 + color: "#DEDEDE" + } + TextEdit { + id: keysText + wrapMode: TextEdit.Wrap + Layout.fillWidth: true; + font.pixelSize: 14 * scaleRatio + textFormat: TextEdit.RichText + readOnly: true + MouseArea { + anchors.fill: parent + onClicked: { + appWindow.showStatusMessage(qsTr("Double tap to copy"),3) + } + onDoubleClicked: { + parent.selectAll() + parent.copy() + parent.deselect() + console.log("copied to clipboard"); + appWindow.showStatusMessage(qsTr("Keys copied to clipboard"),3) + } + } + } + } + + ColumnLayout { + Layout.fillWidth: true + Label { + Layout.fillWidth: true + text: qsTr("Export wallet") + translationManager.emptyString + } + Rectangle { + Layout.fillWidth: true + height: 1 + color: "#DEDEDE" + } + + + RowLayout { + StandardButton { + enabled: !fullWalletQRCode.visible + id: showFullQr + text: qsTr("Spendable Wallet") + translationManager.emptyString + onClicked: { + viewOnlyQRCode.visible = false + } + } + StandardButton { + enabled: fullWalletQRCode.visible + id: showViewOnlyQr + text: qsTr("View Only Wallet") + translationManager.emptyString + onClicked: { + viewOnlyQRCode.visible = true + } + } + Layout.bottomMargin: 30 * scaleRatio + } + + + Image { + visible: !viewOnlyQRCode.visible + id: fullWalletQRCode + Layout.fillWidth: true + Layout.minimumHeight: 180 * scaleRatio + smooth: false + fillMode: Image.PreserveAspectFit + } + + Image { + visible: false + id: viewOnlyQRCode + Layout.fillWidth: true + Layout.minimumHeight: 180 * scaleRatio + smooth: false + fillMode: Image.PreserveAspectFit + } + + Text { + Layout.fillWidth: true + font.bold: true + font.pixelSize: 16 * scaleRatio + text: (viewOnlyQRCode.visible) ? qsTr("View Only Wallet") + translationManager.emptyString : qsTr("Spendable Wallet") + translationManager.emptyString + horizontalAlignment: Text.AlignHCenter + } + } + } + + // fires on every page load + function onPageCompleted() { + console.log("keys page loaded"); + + keysText.text = "" + qsTr("Secret view key") + ": " + currentWallet.secretViewKey + keysText.text += "

" + qsTr("Public view key") + ": " + currentWallet.publicViewKey + keysText.text += (!currentWallet.viewOnly) ? "

" + qsTr("Secret spend key") + ": " + currentWallet.secretSpendKey : "" + keysText.text += "

" + qsTr("Public spend key") + ": " + currentWallet.publicSpendKey + + seedText.text = currentWallet.seed + + if(typeof currentWallet != "undefined") { + viewOnlyQRCode.source = "image://qrcode/monero:" + currentWallet.address+"?secret_view_key="+currentWallet.secretViewKey+"&restore_height="+currentWallet.restoreHeight + fullWalletQRCode.source = viewOnlyQRCode.source +"&secret_spend_key="+currentWallet.secretSpendKey + + if(currentWallet.viewOnly) { + viewOnlyQRCode.visible = true + showFullQr.visible = false + showViewOnlyQr.visible = false + seedText.text = qsTr("(View Only Wallet - No mnemonic seed available)") + translationManager.emptyString + } + } + } + + // fires only once + Component.onCompleted: { + + } + +} + + + + + diff --git a/pages/Mining.qml b/pages/Mining.qml index 2099a45e..0d87310f 100644 --- a/pages/Mining.qml +++ b/pages/Mining.qml @@ -37,6 +37,15 @@ Rectangle { color: "#F0EEEE" property var currentHashRate: 0 + function isDaemonLocal() { + if (appWindow.currentDaemonAddress === "") + return false + var daemonHost = appWindow.currentDaemonAddress.split(":")[0] + if (daemonHost === "127.0.0.1" || daemonHost === "localhost") + return true + return false + } + /* main layout */ ColumnLayout { id: mainLayout diff --git a/pages/Receive.qml b/pages/Receive.qml index 059b725b..b22ddfcd 100644 --- a/pages/Receive.qml +++ b/pages/Receive.qml @@ -169,24 +169,23 @@ Rectangle { ColumnLayout { id: mainLayout anchors.margins: (isMobile)? 17 : 40 - anchors.topMargin: 40 + anchors.topMargin: 40 * scaleRatio anchors.left: parent.left anchors.top: parent.top anchors.right: parent.right - spacing: 20 - property int labelWidth: 120 - property int editWidth: 400 - property int lineEditFontSize: 12 - property int qrCodeSize: 240 + spacing: 20 * scaleRatio + property int labelWidth: 120 * scaleRatio + property int editWidth: 400 * scaleRatio + property int lineEditFontSize: 12 * scaleRatio + property int qrCodeSize: 240 * scaleRatio ColumnLayout { id: addressRow Label { id: addressLabel - fontSize: 14 text: qsTr("Address") + translationManager.emptyString width: mainLayout.labelWidth } @@ -206,6 +205,7 @@ Rectangle { if (addressLine.text.length > 0) { console.log(addressLine.text + " copied to clipboard") clipboard.setText(addressLine.text) + appWindow.showStatusMessage(qsTr("Address copied to clipboard"),3) } } } @@ -218,7 +218,6 @@ Rectangle { Label { Layout.columnSpan: 2 id: paymentIdLabel - fontSize: 14 text: qsTr("Payment ID") + translationManager.emptyString width: mainLayout.labelWidth } @@ -239,6 +238,7 @@ Rectangle { onClicked: { if (paymentIdLine.text.length > 0) { clipboard.setText(paymentIdLine.text) + appWindow.showStatusMessage(qsTr("Payment ID copied to clipboard"),3) } } } @@ -246,7 +246,6 @@ Rectangle { StandardButton { id: generatePaymentId - width: 80 shadowReleasedColor: "#FF4304" shadowPressedColor: "#B32D00" releasedColor: "#FF6C3C" @@ -258,7 +257,6 @@ Rectangle { StandardButton { id: clearPaymentId enabled: !!paymentIdLine.text - width: 80 shadowReleasedColor: "#FF4304" shadowPressedColor: "#B32D00" releasedColor: "#FF6C3C" @@ -272,7 +270,6 @@ Rectangle { id: integratedAddressRow Label { id: integratedAddressLabel - fontSize: 14 text: qsTr("Integrated address") + translationManager.emptyString width: mainLayout.labelWidth } @@ -294,6 +291,7 @@ Rectangle { onClicked: { if (integratedAddressLine.text.length > 0) { clipboard.setText(integratedAddressLine.text) + appWindow.showStatusMessage(qsTr("Integrated address copied to clipboard"),3) } } } @@ -305,7 +303,6 @@ Rectangle { id: amountRow Label { id: amountLabel - fontSize: 14 text: qsTr("Amount") + translationManager.emptyString width: mainLayout.labelWidth } @@ -330,10 +327,9 @@ Rectangle { RowLayout { id: trackingRow - + visible: !isAndroid && !isIOS Label { id: trackingLabel - fontSize: 14 textFormat: Text.RichText text: qsTr("\ Tracking (help)") @@ -392,33 +388,33 @@ Rectangle { } } } - - Menu { - id: qrMenu - title: "QrCode" - MenuItem { - text: qsTr("Save As") + translationManager.emptyString; - onTriggered: qrFileDialog.open() - } - } - - Image { - id: qrCode - anchors.margins: 50 - anchors.top: trackingRow.bottom - Layout.fillWidth: true - Layout.minimumHeight: mainLayout.qrCodeSize - smooth: false - fillMode: Image.PreserveAspectFit - source: "image://qrcode/" + makeQRCodeString() - MouseArea { - anchors.fill: parent - acceptedButtons: Qt.RightButton - onClicked: { - if (mouse.button == Qt.RightButton) - qrMenu.popup() + ColumnLayout { + Menu { + id: qrMenu + title: "QrCode" + MenuItem { + text: qsTr("Save As") + translationManager.emptyString; + onTriggered: qrFileDialog.open() + } + } + + Image { + id: qrCode + anchors.margins: 50 * scaleRatio + Layout.fillWidth: true + Layout.minimumHeight: mainLayout.qrCodeSize + smooth: false + fillMode: Image.PreserveAspectFit + source: "image://qrcode/" + makeQRCodeString() + MouseArea { + anchors.fill: parent + acceptedButtons: Qt.RightButton + onClicked: { + if (mouse.button == Qt.RightButton) + qrMenu.popup() + } + onPressAndHold: qrFileDialog.open() } - onPressAndHold: qrFileDialog.open() } } } diff --git a/pages/Settings.qml b/pages/Settings.qml index 61e1cd16..c020acab 100644 --- a/pages/Settings.qml +++ b/pages/Settings.qml @@ -38,7 +38,6 @@ import "../components" import moneroComponents.Clipboard 1.0 Rectangle { - property var daemonAddress property bool viewOnly: false id: page @@ -48,20 +47,15 @@ Rectangle { function initSettings() { //runs on every page load - - // Daemon settings - daemonAddress = persistentSettings.daemon_address.split(":"); - console.log("address: " + persistentSettings.daemon_address) - // try connecting to daemon } ColumnLayout { id: mainLayout - anchors.margins: 17 + anchors.margins: 17 * scaleRatio anchors.left: parent.left anchors.top: parent.top anchors.right: parent.right - spacing: 10 + spacing: 10 * scaleRatio //! Manage wallet RowLayout { @@ -70,8 +64,7 @@ Rectangle { Layout.fillWidth: true color: "#4A4949" text: qsTr("Manage wallet") + translationManager.emptyString - fontSize: 16 - Layout.topMargin: 10 + Layout.topMargin: 10 * scaleRatio } } @@ -82,7 +75,7 @@ Rectangle { } GridLayout { - columns: (isMobile)? 2 : 4 + columns: (isMobile)? 1 : 4 StandardButton { id: closeWalletButton text: qsTr("Close wallet") + translationManager.emptyString @@ -111,18 +104,6 @@ Rectangle { } } - StandardButton { - id: showSeedButton - shadowReleasedColor: "#FF4304" - shadowPressedColor: "#B32D00" - releasedColor: "#FF6C3C" - pressedColor: "#FF4304" - text: qsTr("Show seed & keys") + translationManager.emptyString - onClicked: { - settingsPasswordDialog.open(); - } - } - /* Rescan cache - Disabled until we know it's needed StandardButton { @@ -160,6 +141,7 @@ Rectangle { */ StandardButton { id: rescanSpentButton + enabled: !persistentSettings.useRemoteNode text: qsTr("Rescan wallet balance") + translationManager.emptyString shadowReleasedColor: "#FF4304" shadowPressedColor: "#B32D00" @@ -184,16 +166,53 @@ Rectangle { } } + RowLayout { + + StandardButton { + id: remoteDisconnect + enabled: persistentSettings.useRemoteNode + Layout.fillWidth: false + text: qsTr("Local Node") + translationManager.emptyString + shadowReleasedColor: "#FF4304" + shadowPressedColor: "#B32D00" + releasedColor: "#FF6C3C" + pressedColor: "#FF4304" + onClicked: { + appWindow.disconnectRemoteNode(); + } + } + + StandardButton { + id: remoteConnect + enabled: !persistentSettings.useRemoteNode + Layout.fillWidth: false + text: qsTr("Remote Node") + translationManager.emptyString + shadowReleasedColor: "#FF4304" + shadowPressedColor: "#B32D00" + releasedColor: "#FF6C3C" + pressedColor: "#FF4304" + onClicked: { + appWindow.connectRemoteNode(); + } + } + } + //! Manage daemon RowLayout { + visible: !isMobile + Layout.topMargin: 20 Label { id: manageDaemonLabel - Layout.fillWidth: true color: "#4A4949" - text: qsTr("Manage daemon") + translationManager.emptyString - fontSize: 16 - anchors.topMargin: 30 - Layout.topMargin: 30 + text: qsTr("Manage Daemon") + translationManager.emptyString + } + + CheckBox { + id: daemonAdvanced + Layout.leftMargin: 15 + text: qsTr("Show advanced") + translationManager.emptyString + checkedIcon: "../images/checkedVioletIcon.png" + uncheckedIcon: "../images/uncheckedIcon.png" } } Rectangle { @@ -203,27 +222,28 @@ Rectangle { } GridLayout { + visible: !isMobile id: daemonStatusRow columns: (isMobile) ? 2 : 4 StandardButton { - visible: true - enabled: !appWindow.daemonRunning + visible: !appWindow.daemonRunning id: startDaemonButton - text: qsTr("Start daemon") + translationManager.emptyString + text: qsTr("Start Local Node") + translationManager.emptyString shadowReleasedColor: "#FF4304" shadowPressedColor: "#B32D00" releasedColor: "#FF6C3C" pressedColor: "#FF4304" onClicked: { + // Set current daemon address to local + appWindow.currentDaemonAddress = appWindow.localDaemonAddress appWindow.startDaemon(daemonFlags.text) } } StandardButton { - visible: true - enabled: appWindow.daemonRunning + visible: appWindow.daemonRunning id: stopDaemonButton - text: qsTr("Stop daemon") + translationManager.emptyString + text: qsTr("Stop Local Node") + translationManager.emptyString shadowReleasedColor: "#FF4304" shadowPressedColor: "#B32D00" releasedColor: "#FF6C3C" @@ -246,16 +266,15 @@ Rectangle { daemonConsolePopup.open(); } } - } - RowLayout { + ColumnLayout { id: blockchainFolderRow + visible: !isMobile Label { id: blockchainFolderLabel color: "#4A4949" text: qsTr("Blockchain location") + translationManager.emptyString - fontSize: 16 } LineEdit { id: blockchainFolder @@ -278,13 +297,14 @@ Rectangle { } } + RowLayout { + visible: daemonAdvanced.checked && !isMobile id: daemonFlagsRow Label { id: daemonFlagsLabel color: "#4A4949" - text: qsTr("Daemon startup flags") + translationManager.emptyString - fontSize: 16 + text: qsTr("Local daemon startup flags") + translationManager.emptyString } LineEdit { id: daemonFlags @@ -297,59 +317,21 @@ Rectangle { RowLayout { Layout.fillWidth: true - spacing: 10 - - Label { - id: daemonAddrLabel - Layout.fillWidth: true - color: "#4A4949" - text: qsTr("Daemon address") + translationManager.emptyString - fontSize: 16 - } - } - - GridLayout { - id: daemonAddrRow - Layout.fillWidth: true - columnSpacing: 10 - columns: (isMobile) ? 2 : 3 - - LineEdit { - id: daemonAddr - Layout.preferredWidth: 100 - Layout.fillWidth: true - text: (daemonAddress !== undefined) ? daemonAddress[0] : "" - placeholderText: qsTr("Hostname / IP") + translationManager.emptyString - } - - - LineEdit { - id: daemonPort - Layout.preferredWidth: 100 - Layout.fillWidth: true - text: (daemonAddress !== undefined) ? daemonAddress[1] : "18081" - placeholderText: qsTr("Port") + translationManager.emptyString - } - } - - RowLayout { - Layout.fillWidth: true - spacing: 10 + visible: daemonAdvanced.checked || isMobile Label { id: daemonLoginLabel Layout.fillWidth: true color: "#4A4949" - text: qsTr("Login (optional)") + translationManager.emptyString - fontSize: 16 + text: qsTr("Node login (optional)") + translationManager.emptyString } } - RowLayout { - + ColumnLayout { + visible: daemonAdvanced.checked || isMobile LineEdit { id: daemonUsername - Layout.preferredWidth: 100 + Layout.preferredWidth: 100 * scaleRatio Layout.fillWidth: true text: persistentSettings.daemonUsername placeholderText: qsTr("Username") + translationManager.emptyString @@ -358,50 +340,65 @@ Rectangle { LineEdit { id: daemonPassword - Layout.preferredWidth: 100 + Layout.preferredWidth: 100 * scaleRatio Layout.fillWidth: true text: persistentSettings.daemonPassword placeholderText: qsTr("Password") + translationManager.emptyString echoMode: TextInput.Password } + } - StandardButton { - id: daemonAddrSave - Layout.fillWidth: false - Layout.leftMargin: 30 - text: qsTr("Connect") + translationManager.emptyString - shadowReleasedColor: "#FF4304" - shadowPressedColor: "#B32D00" - releasedColor: "#FF6C3C" - pressedColor: "#FF4304" - onClicked: { - console.log("saving daemon adress settings") - var newDaemon = daemonAddr.text.trim() + ":" + daemonPort.text.trim() - if(persistentSettings.daemon_address != newDaemon) { - persistentSettings.daemon_address = newDaemon + RowLayout { + visible: persistentSettings.useRemoteNode + ColumnLayout { + Label { + color: "#4A4949" + text: qsTr("Remote node") + translationManager.emptyString + } + RemoteNodeEdit { + id: remoteNodeEdit + Layout.minimumWidth: 100 * scaleRatio + daemonAddrText: persistentSettings.remoteNodeAddress.split(":")[0].trim() + daemonPortText: (persistentSettings.remoteNodeAddress.split(":")[1].trim() == "") ? "18081" : persistentSettings.remoteNodeAddress.split(":")[1] + onEditingFinished: { + persistentSettings.remoteNodeAddress = remoteNodeEdit.getAddress(); + console.log("setting remote node to " + persistentSettings.remoteNodeAddress) } + } - // Update daemon login - persistentSettings.daemonUsername = daemonUsername.text; - persistentSettings.daemonPassword = daemonPassword.text; - currentWallet.setDaemonLogin(persistentSettings.daemonUsername, persistentSettings.daemonPassword); + StandardButton { + id: remoteNodeSave + text: qsTr("Connect") + translationManager.emptyString + shadowReleasedColor: "#FF4304" + shadowPressedColor: "#B32D00" + releasedColor: "#FF6C3C" + pressedColor: "#FF4304" + onClicked: { + // Update daemon login + persistentSettings.remoteNodeAddress = remoteNodeEdit.getAddress(); + persistentSettings.daemonUsername = daemonUsername.text; + persistentSettings.daemonPassword = daemonPassword.text; + persistentSettings.useRemoteNode = true - //Reinit wallet - currentWallet.initAsync(newDaemon); + currentWallet.setDaemonLogin(persistentSettings.daemonUsername, persistentSettings.daemonPassword); + + appWindow.connectRemoteNode() + } } } } RowLayout { + visible: !isMobile Label { color: "#4A4949" text: qsTr("Layout settings") + translationManager.emptyString - fontSize: 16 - anchors.topMargin: 30 - Layout.topMargin: 30 + anchors.topMargin: 30 * scaleRatio + Layout.topMargin: 30 * scaleRatio } } Rectangle { + visible: !isMobile Layout.fillWidth: true height: 1 color: "#DEDEDE" @@ -409,6 +406,7 @@ Rectangle { RowLayout { CheckBox { + visible: !isMobile id: customDecorationsCheckBox checked: persistentSettings.customDecorations onClicked: appWindow.setCustomWindowDecorations(checked) @@ -424,9 +422,8 @@ Rectangle { Label { color: "#4A4949" text: qsTr("Log level") + translationManager.emptyString - fontSize: 16 - anchors.topMargin: 30 - Layout.topMargin: 30 + anchors.topMargin: 30 * scaleRatio + Layout.topMargin: 30 * scaleRatio } } Rectangle { @@ -454,7 +451,6 @@ Rectangle { LineEdit { id: logCategories - Layout.preferredWidth: 200 Layout.fillWidth: true text: appWindow.persistentSettings.logCategories placeholderText: qsTr("(e.g. *:WARNING,net.p2p:DEBUG)") + translationManager.emptyString @@ -475,8 +471,8 @@ Rectangle { color: "#4A4949" text: qsTr("Debug info") + translationManager.emptyString fontSize: 16 - anchors.topMargin: 30 - Layout.topMargin: 30 + anchors.topMargin: 30 * scaleRatio + Layout.topMargin: 30 * scaleRatio } } Rectangle { @@ -484,13 +480,11 @@ Rectangle { height: 1 color: "#DEDEDE" } - TextBlock { Layout.topMargin: 8 Layout.fillWidth: true text: qsTr("GUI version: ") + Version.GUI_VERSION + translationManager.emptyString } - TextBlock { id: guiMoneroVersion Layout.fillWidth: true @@ -588,61 +582,6 @@ Rectangle { } } - PasswordDialog { - id: settingsPasswordDialog - - onAccepted: { - if(appWindow.password === settingsPasswordDialog.password){ - if(currentWallet.seedLanguage == "") { - console.log("No seed language set. Using English as default"); - currentWallet.setSeedLanguage("English"); - } - - seedPopup.title = qsTr("Wallet seed & keys") + translationManager.emptyString; - seedPopup.text = "Wallet Mnemonic seed
" + currentWallet.seed - + "

" + qsTr("Secret view key") + ": " + currentWallet.secretViewKey - + "
" + qsTr("Public view key") + ": " + currentWallet.publicViewKey - + "
" + qsTr("Secret spend key") + ": " + currentWallet.secretSpendKey - + "
" + qsTr("Public spend key") + ": " + currentWallet.publicSpendKey - seedPopup.open() - seedPopup.width = 600 - seedPopup.height = 300 - seedPopup.onCloseCallback = function() { - seedPopup.text = "" - } - - } else { - informationPopup.title = qsTr("Error") + translationManager.emptyString; - informationPopup.text = qsTr("Wrong password"); - informationPopup.open() - informationPopup.onCloseCallback = function() { - settingsPasswordDialog.open() - } - } - - settingsPasswordDialog.password = "" - } - onRejected: { - - } - - } - - StandardDialog { - id: seedPopup - cancelVisible: false - okVisible: true - width:600 - height:400 - - property var onCloseCallback - onAccepted: { - if (onCloseCallback) { - onCloseCallback() - } - } - } - // Choose blockchain folder FileDialog { id: blockchainFileDialog @@ -670,7 +609,6 @@ Rectangle { if(!validator.lmdbExists) { confirmationDialog.text += qsTr("Note: lmdb folder not found. A new folder will be created.") + "\n\n" } - confirmationDialog.icon = StandardIcon.Question confirmationDialog.cancelText = qsTr("Cancel") @@ -703,7 +641,7 @@ Rectangle { function onPageCompleted() { console.log("Settings page loaded"); initSettings(); - viewOnly = currentWallet.viewOnly; + if(typeof daemonManager != "undefined") appWindow.daemonRunning = daemonManager.running(persistentSettings.testnet) @@ -713,8 +651,6 @@ Rectangle { Component.onCompleted: { if(typeof daemonManager != "undefined") daemonManager.daemonConsoleUpdated.connect(onDaemonConsoleUpdated) - - } function onDaemonConsoleUpdated(message){ diff --git a/pages/Sign.qml b/pages/Sign.qml index b8900e7f..3c57fac7 100644 --- a/pages/Sign.qml +++ b/pages/Sign.qml @@ -39,10 +39,6 @@ import moneroComponents.WalletManager 1.0 Rectangle { id: mainLayout - property int labelWidth: 120 -// property int editWidth: 400 - property int lineEditFontSize: 12 - color: "#F0EEEE" Clipboard { id: clipboard } @@ -92,35 +88,31 @@ Rectangle { // sign / verify ColumnLayout { - anchors.margins: 17 + anchors.margins: 17 * scaleRatio anchors.left: parent.left anchors.right: parent.right anchors.top: parent.top anchors.bottom: parent.bottom - spacing: 20 + spacing: 20 * scaleRatio // sign ColumnLayout { id: signBox RowLayout { - ColumnLayout { - spacing: 8 - Label { - text: qsTr("Sign a message or file contents with your address:") + translationManager.emptyString - fontSize: 16 - wrapMode: Text.Wrap - } - Label {} + + Text { + text: qsTr("Sign a message or file contents with your address:") + translationManager.emptyString + wrapMode: Text.Wrap + font.pixelSize: 14 * scaleRatio + Layout.fillWidth: true } } Label { id: signMessageLabel - fontSize: 14 text: qsTr("Either message:") + translationManager.emptyString - width: mainLayout.labelWidth } RowLayout { @@ -133,10 +125,8 @@ Rectangle { id: signMessageLine anchors.left: parent.left anchors.right: signMessageButton.left - fontSize: mainLayout.lineEditFontSize placeholderText: qsTr("Message to sign") + translationManager.emptyString; readOnly: false -// Layout.fillWidth: true onTextChanged: signSignatureLine.text = "" IconButton { @@ -152,7 +142,6 @@ Rectangle { StandardButton { id: signMessageButton anchors.right: parent.right - width: 60 text: qsTr("Sign") + translationManager.emptyString shadowReleasedColor: "#FF4304" shadowPressedColor: "#B32D00" @@ -168,9 +157,7 @@ Rectangle { Label { id: signMessageFileLabel - fontSize: 14 text: qsTr("Or file:") + translationManager.emptyString - width: mainLayout.labelWidth } RowLayout { @@ -192,8 +179,7 @@ Rectangle { StandardButton { id: loadFileToSignButton - anchors.rightMargin: 17 - width: 60 + anchors.rightMargin: 17 * scaleRatio text: qsTr("Select") + translationManager.emptyString shadowReleasedColor: "#FF4304" shadowPressedColor: "#B32D00" @@ -208,7 +194,6 @@ Rectangle { id: signFileLine anchors.left: loadFileToSignButton.right anchors.right: signFileButton.left - fontSize: mainLayout.lineEditFontSize placeholderText: qsTr("Filename with message to sign") + translationManager.emptyString; readOnly: false Layout.fillWidth: true @@ -227,7 +212,6 @@ Rectangle { StandardButton { id: signFileButton anchors.right: parent.right - width: 60 text: qsTr("Sign") + translationManager.emptyString shadowReleasedColor: "#FF4304" shadowPressedColor: "#B32D00" @@ -241,19 +225,17 @@ Rectangle { } } - RowLayout { + ColumnLayout { id: signSignatureRow - anchors.topMargin: 17 + anchors.topMargin: 17 * scaleRatio Label { id: signSignatureLabel - fontSize: 14 text: qsTr("Signature") + translationManager.emptyString } LineEdit { id: signSignatureLine - fontSize: mainLayout.lineEditFontSize placeholderText: qsTr("Signature") + translationManager.emptyString; readOnly: true Layout.fillWidth: true @@ -275,29 +257,24 @@ Rectangle { ColumnLayout { id: verifyBox - RowLayout { - ColumnLayout { - spacing: 8 - Label { - text: qsTr("Verify a message or file signature from an address:") + translationManager.emptyString - fontSize: 16 -// Layout.fillWidth: true - wrapMode: Text.Wrap - } - Label {} + RowLayout { + Text { + text: qsTr("Verify a message or file signature from an address:") + translationManager.emptyString + wrapMode: Text.Wrap + font.pixelSize: 14 * scaleRatio + Layout.fillWidth: true } + } Label { id: verifyMessageLabel - fontSize: 14 text: qsTr("Either message:") + translationManager.emptyString - width: mainLayout.labelWidth } RowLayout { id: verifyMessageRow - anchors.topMargin: 17 + anchors.topMargin: 17 * scaleRatio anchors.left: parent.left anchors.right: parent.right @@ -305,7 +282,6 @@ Rectangle { id: verifyMessageLine anchors.left: parent.left anchors.right: verifyMessageButton.left - fontSize: mainLayout.lineEditFontSize placeholderText: qsTr("Message to verify") + translationManager.emptyString; readOnly: false Layout.fillWidth: true @@ -323,7 +299,6 @@ Rectangle { StandardButton { id: verifyMessageButton anchors.right: parent.right - width: 60 text: qsTr("Verify") + translationManager.emptyString shadowReleasedColor: "#FF4304" shadowPressedColor: "#B32D00" @@ -339,16 +314,14 @@ Rectangle { Label { id: verifyMessageFileLabel - fontSize: 14 text: qsTr("Or file:") + translationManager.emptyString - width: mainLayout.labelWidth } RowLayout { id: verifyFileRow - anchors.topMargin: 17 + anchors.topMargin: 17 * scaleRatio anchors.left: parent.left - anchors.right: parent.right + anchors.right: parent.right FileDialog { id: verifyFileDialog @@ -363,8 +336,7 @@ Rectangle { StandardButton { id: loadFileToVerifyButton - anchors.rightMargin: 17 - width: 60 + anchors.rightMargin: 17 * scaleRatio text: qsTr("Select") + translationManager.emptyString shadowReleasedColor: "#FF4304" shadowPressedColor: "#B32D00" @@ -379,7 +351,6 @@ Rectangle { id: verifyFileLine anchors.left: loadFileToVerifyButton.right anchors.right: verifyFileButton.left - fontSize: mainLayout.lineEditFontSize placeholderText: qsTr("Filename with message to verify") + translationManager.emptyString; readOnly: false Layout.fillWidth: true @@ -397,7 +368,6 @@ Rectangle { StandardButton { id: verifyFileButton anchors.right: parent.right - width: 60 text: qsTr("Verify") + translationManager.emptyString shadowReleasedColor: "#FF4304" shadowPressedColor: "#B32D00" @@ -411,18 +381,16 @@ Rectangle { } } - Label { + Text { id: verifyAddressLabel - fontSize: 14 - width: mainLayout.labelWidth - textFormat: Text.RichText - text: qsTr("\ - Signing address ( Paste in or select from Address book )") + text: qsTr("\ + Signing address ( Paste in or select from Address book )").arg(14 * scaleRatio).arg(2 * scaleRatio).arg(2 * scaleRatio) + translationManager.emptyString -// Layout.fillWidth: true wrapMode: Text.Wrap - - onLinkActivated: appWindow.showPageRequest("AddressBook") + font.pixelSize: 14 * scaleRatio + Layout.fillWidth: true + textFormat: Text.RichText + onLinkActivated: appWindow.showPageRequest("AddressBook") } LineEdit { @@ -430,24 +398,22 @@ Rectangle { anchors.left: parent.left anchors.right: parent.right anchors.top: verifyAddressLabel.bottom - anchors.topMargin: 5 + anchors.topMargin: 5 * scaleRatio placeholderText: "4..." // validator: RegExpValidator { regExp: /[0-9A-Fa-f]{95}/g } } - RowLayout { + ColumnLayout { id: verifySignatureRow - anchors.topMargin: 17 + anchors.topMargin: 17 * scaleRatio Label { id: verifySignatureLabel - fontSize: 14 text: qsTr("Signature") + translationManager.emptyString } LineEdit { id: verifySignatureLine - fontSize: mainLayout.lineEditFontSize placeholderText: qsTr("Signature") + translationManager.emptyString; Layout.fillWidth: true diff --git a/pages/Transfer.qml b/pages/Transfer.qml index 13f20bce..29ca46ed 100644 --- a/pages/Transfer.qml +++ b/pages/Transfer.qml @@ -71,10 +71,10 @@ Rectangle { } function updateMixin() { - var fillLevel = privacyLevelItem.fillLevel + var fillLevel = (isMobile) ? privacyLevelItemSmall.fillLevel : privacyLevelItem.fillLevel var mixin = scaleValueToMixinCount(fillLevel) - print ("PrivacyLevel changed:" + fillLevel) - print ("mixin count: " + mixin) + console.log("PrivacyLevel changed:" + fillLevel) + console.log("mixin count: " + mixin) privacyLabel.text = qsTr("Privacy level (ringsize %1)").arg(mixin+1) + translationManager.emptyString } @@ -107,297 +107,226 @@ Rectangle { } } - Item { + ColumnLayout { id: pageRoot anchors.top: parent.top anchors.left: parent.left anchors.right: parent.right - anchors.topMargin: 20 - height: 400 + anchors.margins: 17 * scaleRatio + spacing: 0 - Label { - id: amountLabel - anchors.left: parent.left - anchors.top: parent.top - anchors.leftMargin: 17 - anchors.rightMargin: 17 - anchors.topMargin: 17 - text: qsTr("Amount") + translationManager.emptyString - fontSize: 14 - } + GridLayout { + columns: (isMobile)? 1 : 2 + Layout.fillWidth: true - Label { - id: transactionPriority - anchors.top: parent.top - anchors.topMargin: 17 - fontSize: 14 - x: (parent.width - 17) / 2 + 17 - text: qsTr("Transaction priority") + translationManager.emptyString - } + ColumnLayout { + Layout.fillWidth: true + Label { + id: amountLabel + text: qsTr("Amount") + translationManager.emptyString + } + + RowLayout { + Layout.fillWidth: true + id: amountRow + Layout.minimumWidth: 200 + Item { + visible: !isMobile + width: 37 * scaleRatio + height: 37 * scaleRatio + + Image { + anchors.centerIn: parent + source: "../images/moneroIcon.png" + } + } + // Amount input + LineEdit { + Layout.fillWidth: true + id: amountLine + placeholderText: qsTr("") + translationManager.emptyString + width:100 + validator: DoubleValidator { + bottom: 0.0 + top: 18446744.073709551615 + decimals: 12 + notation: DoubleValidator.StandardNotation + locale: "C" + } + } + + StandardButton { + id: amountAllButton + width: 60 * scaleRatio + text: qsTr("All") + translationManager.emptyString + shadowReleasedColor: "#FF4304" + shadowPressedColor: "#B32D00" + releasedColor: "#FF6C3C" + pressedColor: "#FF4304" + enabled : true + onClicked: amountLine.text = "(all)" + } + } - Row { - id: amountRow - anchors.top: amountLabel.bottom - anchors.topMargin: 5 - anchors.left: parent.left - anchors.leftMargin: 7 - width: (parent.width - 17) / 2 + 10 - Item { - width: 37 - height: 37 + } - Image { - anchors.centerIn: parent - source: "../images/moneroIcon.png" + ColumnLayout { + Layout.fillWidth: true + Label { + id: transactionPriority + text: qsTr("Transaction priority") + translationManager.emptyString + } + // Note: workaround for translations in listElements + // ListElement: cannot use script for property value, so + // code like this wont work: + // ListElement { column1: qsTr("LOW") + translationManager.emptyString ; column2: ""; priority: PendingTransaction.Priority_Low } + // For translations to work, the strings need to be listed in + // the file components/StandardDropdown.qml too. + + // Priorities before v5 + ListModel { + id: priorityModel + + ListElement { column1: qsTr("Low (x1 fee)") ; column2: ""; priority: PendingTransaction.Priority_Low } + ListElement { column1: qsTr("Medium (x20 fee)") ; column2: ""; priority: PendingTransaction.Priority_Medium } + ListElement { column1: qsTr("High (x166 fee)") ; column2: ""; priority: PendingTransaction.Priority_High } + } + + // Priorites after v5 + ListModel { + id: priorityModelV5 + + ListElement { column1: qsTr("Slow (x0.25 fee)") ; column2: ""; priority: 1} + ListElement { column1: qsTr("Default (x1 fee)") ; column2: ""; priority: 2 } + ListElement { column1: qsTr("Fast (x5 fee)") ; column2: ""; priority: 3 } + ListElement { column1: qsTr("Fastest (x41.5 fee)") ; column2: ""; priority: 4 } + + } + + StandardDropdown { + Layout.fillWidth: true + id: priorityDropdown + shadowReleasedColor: "#FF4304" + shadowPressedColor: "#B32D00" + releasedColor: "#FF6C3C" + pressedColor: "#FF4304" } } - // Amount input - LineEdit { - id: amountLine - placeholderText: qsTr("") + translationManager.emptyString - width: parent.width - 37 - 17 - 60 - validator: DoubleValidator { - bottom: 0.0 - top: 18446744.073709551615 - decimals: 12 - notation: DoubleValidator.StandardNotation - locale: "C" + // Make sure dropdown is on top + z: parent.z + 1 + } + + ColumnLayout { + Layout.fillWidth: true + Label { + id: addressLabel + textFormat: Text.RichText + text: qsTr("\ + Address ( Paste in or select from Address book )") + + translationManager.emptyString + + onLinkActivated: appWindow.showPageRequest("AddressBook") + Layout.fillWidth: true + } + // recipient address input + RowLayout { + id: addressLineRow + Layout.fillWidth: true + + StandardButton { + id: qrfinderButton + text: qsTr("QR Code") + translationManager.emptyString + shadowReleasedColor: "#FF4304" + shadowPressedColor: "#B32D00" + releasedColor: "#FF6C3C" + pressedColor: "#FF4304" + visible : appWindow.qrScannerEnabled + enabled : visible + width: visible ? 60 * scaleRatio : 0 + onClicked: { + cameraUi.state = "Capture" + cameraUi.qrcode_decoded.connect(updateFromQrCode) + } } - } - - StandardButton { - id: amountAllButton - //anchors.left: amountLine.right - //anchors.top: amountLine.top - //anchors.bottom: amountLine.bottom - width: 60 - text: qsTr("All") + translationManager.emptyString - shadowReleasedColor: "#FF4304" - shadowPressedColor: "#B32D00" - releasedColor: "#FF6C3C" - pressedColor: "#FF4304" - enabled : true - onClicked: amountLine.text = "(all)" - } - } - - - // Note: workaround for translations in listElements - // ListElement: cannot use script for property value, so - // code like this wont work: - // ListElement { column1: qsTr("LOW") + translationManager.emptyString ; column2: ""; priority: PendingTransaction.Priority_Low } - // For translations to work, the strings need to be listed in - // the file components/StandardDropdown.qml too. - - // Priorities before v5 - ListModel { - id: priorityModel - - ListElement { column1: qsTr("Low (x1 fee)") ; column2: ""; priority: PendingTransaction.Priority_Low } - ListElement { column1: qsTr("Medium (x20 fee)") ; column2: ""; priority: PendingTransaction.Priority_Medium } - ListElement { column1: qsTr("High (x166 fee)") ; column2: ""; priority: PendingTransaction.Priority_High } - } - - // Priorites after v5 - ListModel { - id: priorityModelV5 - - ListElement { column1: qsTr("Slow (x0.25 fee)") ; column2: ""; priority: 1} - ListElement { column1: qsTr("Default (x1 fee)") ; column2: ""; priority: 2 } - ListElement { column1: qsTr("Fast (x5 fee)") ; column2: ""; priority: 3 } - ListElement { column1: qsTr("Fastest (x41.5 fee)") ; column2: ""; priority: 4 } - - } - - StandardDropdown { - id: priorityDropdown - anchors.top: transactionPriority.bottom - anchors.right: parent.right - anchors.rightMargin: 17 - anchors.topMargin: 5 - anchors.left: transactionPriority.left - shadowReleasedColor: "#FF4304" - shadowPressedColor: "#B32D00" - releasedColor: "#FF6C3C" - pressedColor: "#FF4304" - z: 1 - } - - Label { - id: addressLabel - anchors.left: parent.left - anchors.right: parent.right - anchors.top: amountRow.bottom - anchors.leftMargin: 17 - anchors.rightMargin: 17 - anchors.topMargin: 30 - fontSize: 14 - textFormat: Text.RichText - text: qsTr("\ - Address ( Paste in or select from Address book )") - + translationManager.emptyString - - onLinkActivated: appWindow.showPageRequest("AddressBook") - } - // recipient address input - RowLayout { - id: addressLineRow - anchors.left: parent.left - anchors.right: parent.right - anchors.top: addressLabel.bottom - - StandardButton { - id: qrfinderButton - anchors.left: parent.left - anchors.leftMargin: 17 - anchors.topMargin: 5 - text: qsTr("QR Code") + translationManager.emptyString - shadowReleasedColor: "#FF4304" - shadowPressedColor: "#B32D00" - releasedColor: "#FF6C3C" - pressedColor: "#FF4304" - visible : appWindow.qrScannerEnabled - enabled : visible - width: visible ? 60 : 0 - onClicked: { - cameraUi.state = "Capture" - cameraUi.qrcode_decoded.connect(updateFromQrCode) + LineEdit { + id: addressLine + Layout.fillWidth: true + anchors.topMargin: 5 * scaleRatio + placeholderText: "4..." + // validator: RegExpValidator { regExp: /[0-9A-Fa-f]{95}/g } } - } - LineEdit { - id: addressLine - anchors.left: qrfinderButton.right - anchors.right: resolveButton.left - //anchors.leftMargin: 17 - anchors.topMargin: 5 - placeholderText: "4..." - // validator: RegExpValidator { regExp: /[0-9A-Fa-f]{95}/g } - } - StandardButton { - id: resolveButton - anchors.right: parent.right - anchors.leftMargin: 17 - anchors.topMargin: 17 - anchors.rightMargin: 17 - width: 60 - text: qsTr("Resolve") + translationManager.emptyString - shadowReleasedColor: "#FF4304" - shadowPressedColor: "#B32D00" - releasedColor: "#FF6C3C" - pressedColor: "#FF4304" - enabled : isValidOpenAliasAddress(addressLine.text) - onClicked: { - var result = walletManager.resolveOpenAlias(addressLine.text) - if (result) { - var parts = result.split("|") - if (parts.length == 2) { - var address_ok = walletManager.addressValid(parts[1], appWindow.persistentSettings.testnet) - if (parts[0] === "true") { - if (address_ok) { - addressLine.text = parts[1] - addressLine.cursorPosition = 0 - } - else - oa_message(qsTr("No valid address found at this OpenAlias address")) - } else if (parts[0] === "false") { - if (address_ok) { - addressLine.text = parts[1] - addressLine.cursorPosition = 0 - oa_message(qsTr("Address found, but the DNSSEC signatures could not be verified, so this address may be spoofed")) + StandardButton { + id: resolveButton + width: 60 * scaleRatio + text: qsTr("Resolve") + translationManager.emptyString + shadowReleasedColor: "#FF4304" + shadowPressedColor: "#B32D00" + releasedColor: "#FF6C3C" + pressedColor: "#FF4304" + enabled : isValidOpenAliasAddress(addressLine.text) + onClicked: { + var result = walletManager.resolveOpenAlias(addressLine.text) + if (result) { + var parts = result.split("|") + if (parts.length == 2) { + var address_ok = walletManager.addressValid(parts[1], appWindow.persistentSettings.testnet) + if (parts[0] === "true") { + if (address_ok) { + addressLine.text = parts[1] + addressLine.cursorPosition = 0 + } + else + oa_message(qsTr("No valid address found at this OpenAlias address")) + } else if (parts[0] === "false") { + if (address_ok) { + addressLine.text = parts[1] + addressLine.cursorPosition = 0 + oa_message(qsTr("Address found, but the DNSSEC signatures could not be verified, so this address may be spoofed")) + } else { + oa_message(qsTr("No valid address found at this OpenAlias address, but the DNSSEC signatures could not be verified, so this may be spoofed")) + } + } else { + oa_message(qsTr("Internal error")) + } } else { - oa_message(qsTr("No valid address found at this OpenAlias address, but the DNSSEC signatures could not be verified, so this may be spoofed")) + oa_message(qsTr("Internal error")) } } else { - oa_message(qsTr("Internal error")) + oa_message(qsTr("No address found")) } - } else { - oa_message(qsTr("Internal error")) - } - } else { - oa_message(qsTr("No address found")) } } } - } - Label { - id: paymentIdLabel - anchors.left: parent.left - anchors.right: parent.right - anchors.top: addressLineRow.bottom - anchors.leftMargin: 17 - anchors.rightMargin: 17 - anchors.topMargin: 17 - fontSize: 14 - text: qsTr("Payment ID ( Optional )") + translationManager.emptyString - } + Label { + id: paymentIdLabel + text: qsTr("Payment ID ( Optional )") + translationManager.emptyString + } - // payment id input - LineEdit { - id: paymentIdLine - anchors.left: parent.left - anchors.right: parent.right - anchors.top: paymentIdLabel.bottom - anchors.leftMargin: 17 - anchors.rightMargin: 17 - anchors.topMargin: 5 - placeholderText: qsTr("16 or 64 hexadecimal characters") + translationManager.emptyString - // validator: DoubleValidator { top: 0.0 } - } + // payment id input + LineEdit { + id: paymentIdLine + placeholderText: qsTr("16 or 64 hexadecimal characters") + translationManager.emptyString + Layout.fillWidth: true + } - Label { - id: descriptionLabel - anchors.left: parent.left - anchors.right: parent.right - anchors.top: paymentIdLine.bottom - anchors.leftMargin: 17 - anchors.rightMargin: 17 - anchors.topMargin: 17 - fontSize: 14 - text: qsTr("Description ( Optional )") - + translationManager.emptyString - } + Label { + text: qsTr("Description ( Optional )") + + translationManager.emptyString + } - LineEdit { - id: descriptionLine - anchors.left: parent.left - anchors.right: parent.right - anchors.top: descriptionLabel.bottom - anchors.leftMargin: 17 - anchors.rightMargin: 17 - anchors.topMargin: 5 - placeholderText: qsTr("Saved to local wallet history") + translationManager.emptyString - } - - function checkInformation(amount, address, payment_id, testnet) { - address = address.trim() - payment_id = payment_id.trim() - - var amount_ok = amount.length > 0 - var address_ok = walletManager.addressValid(address, testnet) - var payment_id_ok = payment_id.length == 0 || walletManager.paymentIdValid(payment_id) - var ipid = walletManager.paymentIdFromAddress(address, testnet) - if (ipid.length > 0 && payment_id.length > 0) - payment_id_ok = false - - addressLine.error = !address_ok - amountLine.error = !amount_ok - paymentIdLine.error = !payment_id_ok - - return amount_ok && address_ok && payment_id_ok - } - - - RowLayout { - anchors.left: parent.left - anchors.top: descriptionLine.bottom - anchors.leftMargin: 17 - anchors.topMargin: 17 + LineEdit { + id: descriptionLine + placeholderText: qsTr("Saved to local wallet history") + translationManager.emptyString + Layout.fillWidth: true + } StandardButton { id: sendButton + Layout.bottomMargin: 17 * scaleRatio + Layout.topMargin: 17 * scaleRatio text: qsTr("Send") + translationManager.emptyString shadowReleasedColor: "#FF4304" shadowPressedColor: "#B32D00" @@ -418,6 +347,23 @@ Rectangle { } } + function checkInformation(amount, address, payment_id, testnet) { + address = address.trim() + payment_id = payment_id.trim() + + var amount_ok = amount.length > 0 + var address_ok = walletManager.addressValid(address, testnet) + var payment_id_ok = payment_id.length == 0 || walletManager.paymentIdValid(payment_id) + var ipid = walletManager.paymentIdFromAddress(address, testnet) + if (ipid.length > 0 && payment_id.length > 0) + payment_id_ok = false + + addressLine.error = !address_ok + amountLine.error = !amount_ok + paymentIdLine.error = !payment_id_ok + + return amount_ok && address_ok && payment_id_ok + } } // pageRoot @@ -433,8 +379,10 @@ Rectangle { anchors.top: pageRoot.bottom anchors.left: parent.left anchors.right: parent.right - anchors.margins: 17 - spacing:10 + anchors.leftMargin: 17 * scaleRatio + anchors.topMargin: 17 * scaleRatio + anchors.bottomMargin: 17 * scaleRatio + spacing: 10 * scaleRatio enabled: !viewOnly || pageRoot.enabled RowLayout { @@ -455,13 +403,14 @@ Rectangle { Layout.fillWidth: true height: 1 color: "#DEDEDE" - Layout.bottomMargin: 30 + Layout.bottomMargin: 30 * scaleRatio } RowLayout { visible: persistentSettings.transferShowAdvanced anchors.left: parent.left anchors.right: parent.right + Layout.fillWidth: true Label { id: privacyLabel fontSize: 14 @@ -479,17 +428,27 @@ Rectangle { PrivacyLevel { - visible: persistentSettings.transferShowAdvanced + visible: persistentSettings.transferShowAdvanced && !isMobile id: privacyLevelItem anchors.left: parent.left anchors.right: parent.right + anchors.rightMargin: 17 * scaleRatio + onFillLevelChanged: updateMixin() + } + + PrivacyLevelSmall { + visible: persistentSettings.transferShowAdvanced && isMobile + id: privacyLevelItemSmall + anchors.left: parent.left + anchors.right: parent.right + anchors.rightMargin: 17 * scaleRatio onFillLevelChanged: updateMixin() } GridLayout { visible: persistentSettings.transferShowAdvanced - Layout.topMargin: 50 + Layout.topMargin: 50 * scaleRatio columns: (isMobile) ? 2 : 6 @@ -657,8 +616,8 @@ Rectangle { Rectangle { x: root.width/2 - width/2 y: root.height/2 - height/2 - height:statusText.paintedHeight + 50 - width:statusText.paintedWidth + 40 + height:statusText.paintedHeight + 50 * scaleRatio + width:statusText.paintedWidth + 40 * scaleRatio visible: statusText.text != "" opacity: 0.9 @@ -719,6 +678,7 @@ Rectangle { statusText.text = qsTr("Waiting on daemon synchronization to finish") } else { // everything OK, enable transfer page + // Light wallet is always ready pageRoot.enabled = true; statusText.text = ""; } diff --git a/qml.qrc b/qml.qrc index 06f721c4..7f1b3c37 100644 --- a/qml.qrc +++ b/qml.qrc @@ -72,6 +72,7 @@ images/expandTable.png images/dropdownDel.png components/HistoryTable.qml + components/HistoryTableMobile.qml images/gotoBasicVersion.png images/goToBasicVersionHovered.png BasicPanel.qml @@ -143,5 +144,9 @@ components/Notifier.qml components/MobileHeader.qml components/TextBlock.qml + wizard/WizardDaemonSettings.qml + components/RemoteNodeEdit.qml + pages/Keys.qml + images/menu.png diff --git a/src/QR-Code-scanner/QrCodeScanner.cpp b/src/QR-Code-scanner/QrCodeScanner.cpp index 1129e986..62c639f9 100644 --- a/src/QR-Code-scanner/QrCodeScanner.cpp +++ b/src/QR-Code-scanner/QrCodeScanner.cpp @@ -61,15 +61,23 @@ void QrCodeScanner::processCode(int type, const QString &data) emit notifyError(error); return; } + QVariantMap parsed_unknown_parameters; if(unknown_parameters.size() > 0) { qDebug() << "unknown parameters " << unknown_parameters; + foreach(const QString &item, unknown_parameters ) + { + QStringList parsed_item = item.split("="); + if(parsed_item.size() == 2) { + parsed_unknown_parameters.insert(parsed_item[0], parsed_item[1]); + } + } emit notifyError(error, true); } qDebug() << "Parsed URI : " << address << " " << payment_id << " " << amount << " " << tx_description << " " << recipient_name << " " << error; QString s_amount = WalletManager::instance()->displayAmount(amount); qDebug() << "Amount passed " << s_amount ; - emit decoded(address, payment_id, s_amount, tx_description, recipient_name); + emit decoded(address, payment_id, s_amount, tx_description, recipient_name, parsed_unknown_parameters); } void QrCodeScanner::processFrame(QVideoFrame frame) { @@ -102,3 +110,15 @@ void QrCodeScanner::timerEvent(QTimerEvent *event) } } +QrCodeScanner::~QrCodeScanner() +{ + m_thread->stop(); + m_thread->quit(); + if(!m_thread->wait(5000)) + { + m_thread->terminate(); + m_thread->wait(); + } + +} + diff --git a/src/QR-Code-scanner/QrCodeScanner.h b/src/QR-Code-scanner/QrCodeScanner.h index a6a747b3..a83cf2ac 100644 --- a/src/QR-Code-scanner/QrCodeScanner.h +++ b/src/QR-Code-scanner/QrCodeScanner.h @@ -44,7 +44,7 @@ class QrCodeScanner : public QObject public: QrCodeScanner(QObject *parent = Q_NULLPTR); - + ~QrCodeScanner(); void setSource(QCamera*); bool enabled() const; @@ -57,7 +57,7 @@ public Q_SLOTS: Q_SIGNALS: void enabledChanged(); - void decoded(const QString &address, const QString &payment_id, const QString &amount, const QString &tx_description, const QString &recipient_name); + void decoded(const QString &address, const QString &payment_id, const QString &amount, const QString &tx_description, const QString &recipient_name, const QVariantMap &extra_parameters); void decode(int type, const QString &data); void notifyError(const QString &error, bool warning = false); diff --git a/src/QR-Code-scanner/QrScanThread.cpp b/src/QR-Code-scanner/QrScanThread.cpp index b6e9b9ff..48ecb53a 100644 --- a/src/QR-Code-scanner/QrScanThread.cpp +++ b/src/QR-Code-scanner/QrScanThread.cpp @@ -110,6 +110,7 @@ void QrScanThread::processVideoFrame(const QVideoFrame &frame) void QrScanThread::stop() { m_running = false; + m_waitCondition.wakeOne(); } void QrScanThread::addFrame(const QVideoFrame &frame) @@ -124,9 +125,10 @@ void QrScanThread::run() QVideoFrame frame; while(m_running) { QMutexLocker locker(&m_mutex); - while(m_queue.isEmpty()) + while(m_queue.isEmpty() && m_running) m_waitCondition.wait(&m_mutex); - processVideoFrame(m_queue.takeFirst()); + if(!m_queue.isEmpty()) + processVideoFrame(m_queue.takeFirst()); } } diff --git a/src/QR-Code-scanner/QrScanThread.h b/src/QR-Code-scanner/QrScanThread.h index 03f17be3..296e1dc5 100644 --- a/src/QR-Code-scanner/QrScanThread.h +++ b/src/QR-Code-scanner/QrScanThread.h @@ -44,6 +44,7 @@ class QrScanThread : public QThread, public zbar::Image::Handler public: QrScanThread(QObject *parent = Q_NULLPTR); void addFrame(const QVideoFrame &frame); + virtual void stop(); Q_SIGNALS: void decoded(int type, const QString &data); @@ -51,7 +52,6 @@ Q_SIGNALS: protected: virtual void run(); - virtual void stop(); void processVideoFrame(const QVideoFrame &); void processQImage(const QImage &); void processZImage(zbar::Image &image); diff --git a/src/libwalletqt/TransactionInfo.cpp b/src/libwalletqt/TransactionInfo.cpp index 5ad739bf..a787b151 100644 --- a/src/libwalletqt/TransactionInfo.cpp +++ b/src/libwalletqt/TransactionInfo.cpp @@ -38,6 +38,8 @@ QString TransactionInfo::displayAmount() const QString TransactionInfo::fee() const { + if(m_pimpl->fee() == 0) + return ""; return WalletManager::instance()->displayAmount(m_pimpl->fee()); } diff --git a/src/libwalletqt/Wallet.cpp b/src/libwalletqt/Wallet.cpp index 409d3d1a..e5ffef82 100644 --- a/src/libwalletqt/Wallet.cpp +++ b/src/libwalletqt/Wallet.cpp @@ -646,6 +646,8 @@ Wallet::Wallet(Monero::Wallet *w, QObject *parent) Wallet::~Wallet() { qDebug("~Wallet: Closing wallet"); + delete m_addressBook; + m_addressBook = NULL; delete m_history; m_history = NULL; diff --git a/src/libwalletqt/WalletManager.cpp b/src/libwalletqt/WalletManager.cpp index 396274c6..a8346478 100644 --- a/src/libwalletqt/WalletManager.cpp +++ b/src/libwalletqt/WalletManager.cpp @@ -275,6 +275,11 @@ bool WalletManager::stopMining() return m_pimpl->stopMining(); } +bool WalletManager::localDaemonSynced() const +{ + return blockchainHeight() > 1 && blockchainHeight() >= blockchainTargetHeight(); +} + QString WalletManager::resolveOpenAlias(const QString &address) const { bool dnssec_valid = false; diff --git a/src/libwalletqt/WalletManager.h b/src/libwalletqt/WalletManager.h index bf9bf19a..69c63b05 100644 --- a/src/libwalletqt/WalletManager.h +++ b/src/libwalletqt/WalletManager.h @@ -112,6 +112,7 @@ public: Q_INVOKABLE quint64 blockchainHeight() const; Q_INVOKABLE quint64 blockchainTargetHeight() const; Q_INVOKABLE double miningHashRate() const; + Q_INVOKABLE bool localDaemonSynced() const; Q_INVOKABLE bool isMining() const; Q_INVOKABLE bool startMining(const QString &address, quint32 threads, bool backgroundMining, bool ignoreBattery); diff --git a/wizard/WizardCreateWallet.qml b/wizard/WizardCreateWallet.qml index 99ee2478..14142212 100644 --- a/wizard/WizardCreateWallet.qml +++ b/wizard/WizardCreateWallet.qml @@ -78,7 +78,7 @@ ColumnLayout { // page submitted or b) delete it when program closed before reaching final page // Always delete the wallet object before creating new - we could be stepping back from recovering wallet - if (typeof settingsObject.wallet !== 'undefined') { + if (typeof m_wallet !== 'undefined') { walletManager.closeWallet() console.log("deleting wallet") } @@ -91,8 +91,8 @@ ColumnLayout { uiItem.wordsTextItem.memoText = wallet.seed // saving wallet in "global" settings object // TODO: wallet should have a property pointing to the file where it stored or loaded from - settingsObject.wallet = wallet - settingsObject.tmp_wallet_filename = tmp_wallet_filename + m_wallet = wallet; + settingsObject['tmp_wallet_filename'] = tmp_wallet_filename } WizardManageWalletUI { diff --git a/wizard/WizardDaemonSettings.qml b/wizard/WizardDaemonSettings.qml new file mode 100644 index 00000000..db6d1f62 --- /dev/null +++ b/wizard/WizardDaemonSettings.qml @@ -0,0 +1,198 @@ +// Copyright (c) 2014-2017, The Monero Project +// +// All rights reserved. +// +// Redistribution and use in source and binary forms, with or without modification, are +// permitted provided that the following conditions are met: +// +// 1. Redistributions of source code must retain the above copyright notice, this list of +// conditions and the following disclaimer. +// +// 2. Redistributions in binary form must reproduce the above copyright notice, this list +// of conditions and the following disclaimer in the documentation and/or other +// materials provided with the distribution. +// +// 3. Neither the name of the copyright holder nor the names of its contributors may be +// used to endorse or promote products derived from this software without specific +// prior written permission. +// +// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY +// EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF +// MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL +// THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, +// PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS +// INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, +// STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF +// THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + +import moneroComponents.WalletManager 1.0 +import QtQuick 2.2 +import QtQuick.Layouts 1.1 +import "../components" +import "utils.js" as Utils + +ColumnLayout { + Layout.leftMargin: wizardLeftMargin + Layout.rightMargin: wizardRightMargin + + id: passwordPage + opacity: 0 + visible: false + property alias titleText: titleText.text + Behavior on opacity { + NumberAnimation { duration: 100; easing.type: Easing.InQuad } + } + + onOpacityChanged: visible = opacity !== 0 + + + function onPageOpened(settingsObject) { + } + function onWizardRestarted(){ + } + + function onPageClosed(settingsObject) { + appWindow.persistentSettings.useRemoteNode = remoteNode.checked + appWindow.persistentSettings.startLocalNode = localNode.checked + appWindow.persistentSettings.remoteNodeAddress = remoteNodeEdit.getAddress(); + return true + } + + RowLayout { + id: dotsRow + Layout.alignment: Qt.AlignRight + + ListModel { + id: dotsModel + ListElement { dotColor: "#36B05B" } + ListElement { dotColor: "#36B05B" } + ListElement { dotColor: "#FFE00A" } + ListElement { dotColor: "#DBDBDB" } + } + + Repeater { + model: dotsModel + delegate: Rectangle { + // Password page is last page when creating view only wallet + // TODO: make this dynamic for all pages in wizard + visible: (wizard.currentPath != "create_view_only_wallet" || index < 2) + width: 12; height: 12 + radius: 6 + color: dotColor + } + } + } + + ColumnLayout { + id: headerColumn + + Text { + Layout.fillWidth: true + id: titleText + font.family: "Arial" + font.pixelSize: 28 * scaleRatio + wrapMode: Text.Wrap + horizontalAlignment: Text.AlignHCenter + //renderType: Text.NativeRendering + color: "#3F3F3F" + text: "Daemon settings" + + } + + Text { + Layout.fillWidth: true + Layout.topMargin: 30 * scaleRatio + Layout.bottomMargin: 30 * scaleRatio + font.family: "Arial" + font.pixelSize: 18 * scaleRatio + wrapMode: Text.Wrap + //renderType: Text.NativeRendering + color: "#4A4646" + textFormat: Text.RichText +// horizontalAlignment: Text.AlignHCenter + text: qsTr("To be able to communicate with the Monero network your wallet needs to be connected to a Monero node. For best privacy it's recommended to run your own node. \ +

\ + If you don't have the option to run an own node there's an option to connect to a remote node.") + + translationManager.emptyString + } + } + + ColumnLayout { + + RowLayout { + CheckBox { + id: localNode + text: qsTr("Start a node automatically in background (recommended)") + translationManager.emptyString + background: "#FFFFFF" + fontColor: "#4A4646" + fontSize: 16 * scaleRatio + checkedIcon: "../images/checkedVioletIcon.png" + uncheckedIcon: "../images/uncheckedIcon.png" + checked: appWindow.persistentSettings.startLocalNode && !isAndroid && !isIOS + visible: !isAndroid && !isIOS + } + } + + ColumnLayout { + visible: localNode.checked + id: blockchainFolderRow + Label { + Layout.fillWidth: true + Layout.topMargin: 20 * scaleRatio + fontSize: 14 * scaleRatio + text: qsTr("Blockchain location") + translationManager.emptyString + } + LineEdit { + id: blockchainFolder + Layout.preferredWidth: 200 * scaleRatio + Layout.fillWidth: true + text: persistentSettings.blockchainDataDir + placeholderText: qsTr("(optional)") + translationManager.emptyString + + MouseArea { + anchors.fill: parent + onClicked: { + mouse.accepted = false + if(persistentSettings.blockchainDataDir != "") + blockchainFileDialog.folder = "file://" + persistentSettings.blockchainDataDir + blockchainFileDialog.open() + blockchainFolder.focus = true + } + } + + } + } + + RowLayout { + CheckBox { + id: remoteNode + text: (localNode.checked) ? qsTr("Connect to a remote node until my own node has finished syncing") + translationManager.emptyString + : qsTr("Connect to a remote node") + translationManager.emptyString + Layout.topMargin: 20 * scaleRatio + background: "#FFFFFF" + fontColor: "#4A4646" + fontSize: 16 * scaleRatio + checkedIcon: "../images/checkedVioletIcon.png" + uncheckedIcon: "../images/uncheckedIcon.png" + checked: appWindow.persistentSettings.useRemoteNode + } + + } + + RowLayout { + RemoteNodeEdit { + Layout.minimumWidth: 300 * scaleRatio + opacity: remoteNode.checked + id: remoteNodeEdit + daemonAddrText: persistentSettings.remoteNodeAddress.split(":")[0].trim() + daemonPortText: (persistentSettings.remoteNodeAddress.split(":")[1].trim() == "") ? "18081" : persistentSettings.remoteNodeAddress.split(":")[1] + } + } + } + + + Component.onCompleted: { + parent.wizardRestarted.connect(onWizardRestarted) + } +} diff --git a/wizard/WizardFinish.qml b/wizard/WizardFinish.qml index a6999e9c..794a5cf9 100644 --- a/wizard/WizardFinish.qml +++ b/wizard/WizardFinish.qml @@ -100,7 +100,7 @@ ColumnLayout { ListElement { dotColor: "#36B05B" } ListElement { dotColor: "#36B05B" } ListElement { dotColor: "#36B05B" } - //ListElement { dotColor: "#36B05B" } + ListElement { dotColor: "#FFE00A" } } Repeater { @@ -120,7 +120,7 @@ ColumnLayout { Text { Layout.fillWidth: true font.family: "Arial" - font.pixelSize: 28 + font.pixelSize: 28 * scaleRatio wrapMode: Text.Wrap horizontalAlignment: Text.AlignHCenter //renderType: Text.NativeRendering @@ -132,7 +132,7 @@ ColumnLayout { Layout.fillWidth: true id: settingsText font.family: "Arial" - font.pixelSize: 16 + font.pixelSize: 16 * scaleRatio wrapMode: Text.Wrap textFormat: Text.RichText horizontalAlignment: Text.AlignHLeft diff --git a/wizard/WizardMain.qml b/wizard/WizardMain.qml index 1b16acfd..d5c9eb11 100644 --- a/wizard/WizardMain.qml +++ b/wizard/WizardMain.qml @@ -40,17 +40,19 @@ ColumnLayout { property alias nextButton : nextButton property var settings : ({}) property int currentPage: 0 - property int wizardLeftMargin: (!isMobile) ? 150 : 25 - property int wizardRightMargin: (!isMobile) ? 150 : 25 - property int wizardBottomMargin: (isMobile) ? 150 : 25 - property int wizardTopMargin: (isMobile) ? 15 : 50 + property int wizardLeftMargin: (!isMobile) ? 150 : 25 * scaleRatio + property int wizardRightMargin: (!isMobile) ? 150 : 25 * scaleRatio + property int wizardBottomMargin: (isMobile) ? 150 : 25 * scaleRatio + property int wizardTopMargin: (isMobile) ? 15 * scaleRatio : 50 + // Storing wallet in Settings object doesn't work in qt 5.8 on android + property var m_wallet; property var paths: { // "create_wallet" : [welcomePage, optionsPage, createWalletPage, passwordPage, donationPage, finishPage ], // "recovery_wallet" : [welcomePage, optionsPage, recoveryWalletPage, passwordPage, donationPage, finishPage ], // disable donation page - "create_wallet" : [welcomePage, optionsPage, createWalletPage, passwordPage, finishPage ], - "recovery_wallet" : [welcomePage, optionsPage, recoveryWalletPage, passwordPage, finishPage ], + "create_wallet" : [welcomePage, optionsPage, createWalletPage, passwordPage, daemonSettingsPage, finishPage ], + "recovery_wallet" : [welcomePage, optionsPage, recoveryWalletPage, passwordPage, daemonSettingsPage, finishPage ], "create_view_only_wallet" : [ createViewOnlyWalletPage, passwordPage ], } @@ -81,6 +83,10 @@ ColumnLayout { } function switchPage(next) { + + // Android focus workaround + releaseFocus(); + // save settings for current page; if (next && typeof pages[currentPage].onPageClosed !== 'undefined') { if (pages[currentPage].onPageClosed(settings) !== true) { @@ -130,6 +136,8 @@ ColumnLayout { print ("show recovery wallet page"); currentPath = "recovery_wallet" pages = paths[currentPath] + // Create temporary wallet + createWalletPage.createWallet(settings) wizard.nextButton.visible = true // goto next page switchPage(true); @@ -137,9 +145,8 @@ ColumnLayout { function openOpenWalletPage() { console.log("open wallet from file page"); - if (typeof wizard.settings['wallet'] !== 'undefined') { - settings.wallet.destroy(); - delete wizard.settings['wallet']; + if (typeof m_wallet !== 'undefined' && m_wallet != null) { + walletManager.closeWallet() } optionsPage.onPageClosed(settings) wizard.openWalletFromFileClicked(); @@ -203,11 +210,10 @@ ColumnLayout { var new_wallet_filename = createWalletPath(settings.wallet_path,settings.account_name) if(isIOS) { console.log("saving in ios: "+ moneroAccountsDir + new_wallet_filename) - settings.wallet.store(moneroAccountsDir + new_wallet_filename); + m_wallet.store(moneroAccountsDir + new_wallet_filename); } else { console.log("saving in wizard: "+ new_wallet_filename) - settings.wallet.store(new_wallet_filename); - + m_wallet.store(new_wallet_filename); } @@ -217,7 +223,7 @@ ColumnLayout { oshelper.removeTemporaryWallet(settings.tmp_wallet_filename) // protecting wallet with password - settings.wallet.setPassword(settings.wallet_password); + m_wallet.setPassword(settings.wallet_password); // Store password in session to be able to use password protected functions (e.g show seed) appWindow.password = settings.wallet_password @@ -291,6 +297,12 @@ ColumnLayout { Layout.topMargin: wizardTopMargin } + WizardDaemonSettings { + id: daemonSettingsPage + Layout.bottomMargin: wizardBottomMargin + Layout.topMargin: wizardTopMargin + } + WizardDonation { id: donationPage Layout.bottomMargin: wizardBottomMargin @@ -308,10 +320,10 @@ ColumnLayout { anchors.verticalCenter: wizard.verticalCenter anchors.left: parent.left anchors.leftMargin: isMobile ? 20 : 50 - anchors.bottomMargin: isMobile ? 20 : 50 + anchors.bottomMargin: isMobile ? 20 * scaleRatio : 50 visible: parent.currentPage > 0 - width: 50; height: 50 + width: 50 * scaleRatio; height: 50 * scaleRatio radius: 25 color: prevArea.containsMouse ? "#FF4304" : "#FF6C3C" @@ -333,10 +345,10 @@ ColumnLayout { id: nextButton anchors.verticalCenter: wizard.verticalCenter anchors.right: parent.right - anchors.rightMargin: isMobile ? 20 : 50 - anchors.bottomMargin: isMobile ? 20 : 50 + anchors.rightMargin: isMobile ? 20 * scaleRatio : 50 + anchors.bottomMargin: isMobile ? 20 * scaleRatio : 50 visible: currentPage > 1 && currentPage < pages.length - 1 - width: 50; height: 50 + width: 50 * scaleRatio; height: 50 * scaleRatio radius: 25 color: enabled ? nextArea.containsMouse ? "#FF4304" : "#FF6C3C" : "#DBDBDB" @@ -359,7 +371,7 @@ ColumnLayout { id: sendButton anchors.right: parent.right anchors.bottom: parent.bottom - anchors.margins: (isMobile) ? 20 : 50 + anchors.margins: (isMobile) ? 20 * scaleRatio : 50 * scaleRatio text: qsTr("USE MONERO") + translationManager.emptyString shadowReleasedColor: "#FF4304" shadowPressedColor: "#B32D00" @@ -376,7 +388,7 @@ ColumnLayout { id: createViewOnlyWalletButton anchors.right: parent.right anchors.bottom: parent.bottom - anchors.margins: (isMobile) ? 20 : 50 + anchors.margins: (isMobile) ? 20 * scaleRatio : 50 text: qsTr("Create wallet") + translationManager.emptyString shadowReleasedColor: "#FF4304" shadowPressedColor: "#B32D00" @@ -408,7 +420,7 @@ ColumnLayout { id: abortViewOnlyButton anchors.right: createViewOnlyWalletButton.left anchors.bottom: parent.bottom - anchors.margins: (isMobile) ? 20 : 50 + anchors.margins: (isMobile) ? 20 * scaleRatio : 50 text: qsTr("Abort") + translationManager.emptyString shadowReleasedColor: "#FF4304" shadowPressedColor: "#B32D00" diff --git a/wizard/WizardManageWalletUI.qml b/wizard/WizardManageWalletUI.qml index 9b3f206d..fb714ad0 100644 --- a/wizard/WizardManageWalletUI.qml +++ b/wizard/WizardManageWalletUI.qml @@ -86,6 +86,31 @@ ColumnLayout { return wordsArray.length === 25 } + function updateFromQrCode(address, payment_id, amount, tx_description, recipient_name, extra_parameters) { + // Switch to recover from keys + recoverFromSeedMode = false + spendKeyLine.text = "" + viewKeyLine.text = "" + restoreHeightItem.text = "" + + + if(typeof extra_parameters.secret_view_key != "undefined") { + viewKeyLine.text = extra_parameters.secret_view_key + } + if(typeof extra_parameters.secret_spend_key != "undefined") { + spendKeyLine.text = extra_parameters.secret_spend_key + } + if(typeof extra_parameters.restore_height != "undefined") { + restoreHeightItem.text = extra_parameters.restore_height + } + addressLine.text = address + + cameraUi.qrcode_decoded.disconnect(updateFromQrCode) + + // Check if keys are correct + checkNextButton(); + } + RowLayout { id: dotsRow Layout.alignment: Qt.AlignRight @@ -93,8 +118,8 @@ ColumnLayout { ListModel { id: dotsModel - ListElement { dotColor: "#36B05B" } - //ListElement { dotColor: "#DBDBDB" } + ListElement { dotColor: "#FFE00A" } + ListElement { dotColor: "#DBDBDB" } ListElement { dotColor: "#DBDBDB" } ListElement { dotColor: "#DBDBDB" } } @@ -118,7 +143,7 @@ ColumnLayout { horizontalAlignment: Text.AlignHCenter id: titleText font.family: "Arial" - font.pixelSize: 28 + font.pixelSize: 28 * scaleRatio wrapMode: Text.Wrap color: "#3F3F3F" } @@ -128,8 +153,8 @@ ColumnLayout { Layout.bottomMargin: rowSpacing Label { - Layout.topMargin: 20 - fontSize: 14 + Layout.topMargin: 20 * scaleRatio + fontSize: 14 * scaleRatio text: qsTr("Wallet name") + translationManager.emptyString } @@ -137,16 +162,17 @@ ColumnLayout { LineEdit { id: accountName Layout.fillWidth: true - Layout.maximumWidth: 600 - Layout.minimumWidth: 200 + Layout.maximumWidth: 600 * scaleRatio + Layout.minimumWidth: 200 * scaleRatio text: defaultAccountName onTextUpdated: checkNextButton() } } - RowLayout{ + GridLayout{ + columns: (isMobile)? 2 : 4 visible: recoverMode - spacing: 0 + StandardButton { id: recoverFromSeedButton text: qsTr("Restore from seed") + translationManager.emptyString @@ -174,6 +200,22 @@ ColumnLayout { checkNextButton(); } } + + StandardButton { + id: qrfinderButton + text: qsTr("From QR Code") + translationManager.emptyString + shadowReleasedColor: "#FF4304" + shadowPressedColor: "#B32D00" + releasedColor: "#FF6C3C" + pressedColor: "#FF4304" + visible : true //appWindow.qrScannerEnabled + enabled : visible + onClicked: { + cameraUi.state = "Capture" + cameraUi.qrcode_decoded.connect(updateFromQrCode) + } + } + } // Recover from seed @@ -183,8 +225,8 @@ ColumnLayout { WizardMemoTextInput { id : memoTextItem Layout.fillWidth: true - Layout.maximumWidth: 600 - Layout.minimumWidth: 200 + Layout.maximumWidth: 600 * scaleRatio + Layout.minimumWidth: 200 * scaleRatio } } @@ -198,24 +240,24 @@ ColumnLayout { LineEdit { Layout.fillWidth: true id: addressLine - Layout.maximumWidth: 600 - Layout.minimumWidth: 200 + Layout.maximumWidth: 600 * scaleRatio + Layout.minimumWidth: 200 * scaleRatio placeholderText: qsTr("Account address (public)") + translationManager.emptyString onTextUpdated: checkNextButton() } LineEdit { Layout.fillWidth: true id: viewKeyLine - Layout.maximumWidth: 600 - Layout.minimumWidth: 200 + Layout.maximumWidth: 600 * scaleRatio + Layout.minimumWidth: 200 * scaleRatio placeholderText: qsTr("View key (private)") + translationManager.emptyString onTextUpdated: checkNextButton() } LineEdit { Layout.fillWidth: true - Layout.maximumWidth: 600 - Layout.minimumWidth: 200 + Layout.maximumWidth: 600 * scaleRatio + Layout.minimumWidth: 200 * scaleRatio id: spendKeyLine placeholderText: qsTr("Spend key (private)") + translationManager.emptyString onTextUpdated: checkNextButton() @@ -227,8 +269,8 @@ ColumnLayout { LineEdit { id: restoreHeightItem Layout.fillWidth: true - Layout.maximumWidth: 600 - Layout.minimumWidth: 200 + Layout.maximumWidth: 600 * scaleRatio + Layout.minimumWidth: 200 * scaleRatio placeholderText: qsTr("Restore height (optional)") + translationManager.emptyString validator: IntValidator { bottom:0 @@ -240,15 +282,15 @@ ColumnLayout { ColumnLayout { Label { Layout.fillWidth: true - Layout.topMargin: 20 + Layout.topMargin: 20 * scaleRatio fontSize: 14 text: qsTr("Your wallet is stored in") + ": " + fileUrlInput.text; } LineEdit { Layout.fillWidth: true - Layout.maximumWidth: 600 - Layout.minimumWidth: 200 + Layout.maximumWidth: 600 * scaleRatio + Layout.minimumWidth: 200 * scaleRatio id: fileUrlInput text: moneroAccountsDir + "/" diff --git a/wizard/WizardMemoTextInput.qml b/wizard/WizardMemoTextInput.qml index e9fbfdc9..d278508e 100644 --- a/wizard/WizardMemoTextInput.qml +++ b/wizard/WizardMemoTextInput.qml @@ -25,21 +25,21 @@ Column { TextEdit { id: memoTextInput property alias placeholderText: memoTextPlaceholder.text - textMargin: 8 + textMargin: 8 * scaleRatio text: "" font.family: "Arial" - font.pixelSize: 16 + font.pixelSize: 16 * scaleRatio wrapMode: TextInput.Wrap width: parent.width selectByMouse: true - property int minimumHeight: 100 + property int minimumHeight: 100 * scaleRatio height: contentHeight > minimumHeight ? contentHeight : minimumHeight Text { id: memoTextPlaceholder anchors.fill:parent - font.pixelSize: 16 - anchors.margins: 8 + font.pixelSize: 16 * scaleRatio + anchors.margins: 8 * scaleRatio font.bold:true text: qsTr("Enter your 25 word mnemonic seed") + translationManager.emptyString color: "#BABABA" @@ -59,7 +59,11 @@ Column { MouseArea { anchors.fill: parent cursorShape: Qt.PointingHandCursor - onClicked: clipboard.setText(memoTextInput.text) + onClicked: { + clipboard.setText(memoTextInput.text) + appWindow.showStatusMessage(qsTr("Seed copied to clipboard"),3) + } + } } Rectangle { @@ -75,14 +79,14 @@ Column { Text { id: wordsTipText anchors.fill: parent - anchors.topMargin : 16 - anchors.bottomMargin: 16 - anchors.leftMargin: 16 - anchors.rightMargin: 16 + anchors.topMargin : 16 * scaleRatio + anchors.bottomMargin: 16 * scaleRatio + anchors.leftMargin: 16 * scaleRatio + anchors.rightMargin: 16 * scaleRatio verticalAlignment: Text.AlignVCenter horizontalAlignment: Text.AlignHCenter font.family: "Arial" - font.pixelSize: 15 + font.pixelSize: 15 * scaleRatio color: "#4A4646" wrapMode: Text.Wrap text: qsTr("This seed is very important to write down and keep secret. It is all you need to backup and restore your wallet.") diff --git a/wizard/WizardOptions.qml b/wizard/WizardOptions.qml index 0154b9ce..8f4e9477 100644 --- a/wizard/WizardOptions.qml +++ b/wizard/WizardOptions.qml @@ -38,8 +38,8 @@ ColumnLayout { signal openWalletClicked() opacity: 0 visible: false - property int buttonSize: (isMobile) ? 80 : 190 - property int buttonImageSize: (isMobile) ? buttonSize - 10 : buttonSize - 30 + property int buttonSize: (isMobile) ? 80 * scaleRatio : 190 * scaleRatio + property int buttonImageSize: (isMobile) ? buttonSize - 10 * scaleRatio : buttonSize - 30 * scaleRatio function onPageClosed() { // Save settings used in open from file. @@ -60,13 +60,13 @@ ColumnLayout { id: headerColumn Layout.leftMargin: wizardLeftMargin Layout.rightMargin: wizardRightMargin - Layout.bottomMargin: (!isMobile) ? 40 : 20 - spacing: 30 + Layout.bottomMargin: (!isMobile) ? 40 * scaleRatio : 20 + spacing: 30 * scaleRatio Text { Layout.fillWidth: true font.family: "Arial" - font.pixelSize: 28 + font.pixelSize: 28 * scaleRatio //renderType: Text.NativeRendering color: "#3F3F3F" wrapMode: Text.Wrap @@ -77,7 +77,7 @@ ColumnLayout { Text { Layout.fillWidth: true font.family: "Arial" - font.pixelSize: 18 + font.pixelSize: 18 * scaleRatio //renderType: Text.NativeRendering color: "#4A4646" wrapMode: Text.Wrap @@ -91,8 +91,8 @@ ColumnLayout { Layout.rightMargin: wizardRightMargin Layout.alignment: Qt.AlignCenter id: actionButtons - columnSpacing: 40 - rowSpacing: 10 + columnSpacing: 40 * scaleRatio + rowSpacing: 10 * scaleRatio Layout.fillWidth: true Layout.fillHeight: true flow: isMobile ? GridLayout.TopToBottom : GridLayout.LeftToRight @@ -101,8 +101,8 @@ ColumnLayout { Layout.fillHeight: true Layout.fillWidth: true flow: !isMobile ? GridLayout.TopToBottom : GridLayout.LeftToRight - rowSpacing: 20 - columnSpacing: 10 + rowSpacing: 20 * scaleRatio + columnSpacing: 10 * scaleRatio Rectangle { Layout.preferredHeight: page.buttonSize @@ -132,9 +132,9 @@ ColumnLayout { } Text { - Layout.preferredWidth: 190 + Layout.preferredWidth: page.buttonSize font.family: "Arial" - font.pixelSize: 16 + font.pixelSize: 16 * scaleRatio color: "#4A4949" horizontalAlignment: Text.AlignHCenter wrapMode: Text.WordWrap @@ -146,8 +146,8 @@ ColumnLayout { Layout.fillWidth: true Layout.fillHeight: true flow: !isMobile ? GridLayout.TopToBottom : GridLayout.LeftToRight - rowSpacing: 20 - columnSpacing: 10 + rowSpacing: 20 * scaleRatio + columnSpacing: 10 * scaleRatio Rectangle { Layout.preferredHeight: page.buttonSize @@ -156,7 +156,7 @@ ColumnLayout { color: recoverWalletArea.containsMouse ? "#DBDBDB" : "#FFFFFF" Image { - width: page.buttomImageSize + width: page.buttonImageSize height: page.buttonImageSize fillMode: Image.PreserveAspectFit anchors.centerIn: parent @@ -174,9 +174,9 @@ ColumnLayout { } Text { - Layout.preferredWidth: 190 + Layout.preferredWidth: page.buttonSize font.family: "Arial" - font.pixelSize: 16 + font.pixelSize: 16 * scaleRatio color: "#4A4949" horizontalAlignment: Text.AlignHCenter text: qsTr("Restore wallet from keys or mnemonic seed") + translationManager.emptyString @@ -189,8 +189,8 @@ ColumnLayout { Layout.fillHeight: true Layout.fillWidth: true flow: !isMobile ? GridLayout.TopToBottom : GridLayout.LeftToRight - rowSpacing: 20 - columnSpacing: 10 + rowSpacing: 20 * scaleRatio + columnSpacing: 10 * scaleRatio Rectangle { Layout.preferredHeight: page.buttonSize @@ -217,9 +217,9 @@ ColumnLayout { } Text { - Layout.preferredWidth: 190 + Layout.preferredWidth: page.buttonSize font.family: "Arial" - font.pixelSize: 16 + font.pixelSize: 16 * scaleRatio color: "#4A4949" horizontalAlignment: Text.AlignHCenter text: qsTr("Open a wallet from file") + translationManager.emptyString @@ -234,18 +234,18 @@ ColumnLayout { RowLayout { Layout.leftMargin: wizardLeftMargin Layout.rightMargin: wizardRightMargin - Layout.topMargin: 30 + Layout.topMargin: 30 * scaleRatio Layout.alignment: Qt.AlignCenter Layout.fillWidth: true Rectangle { - width: 100 + width: 100 * scaleRatio CheckBox { id: testNet text: qsTr("Testnet") + translationManager.emptyString background: "#FFFFFF" fontColor: "#4A4646" - fontSize: 16 + fontSize: 16 * scaleRatio checkedIcon: "../images/checkedVioletIcon.png" uncheckedIcon: "../images/uncheckedIcon.png" checked: appWindow.persistentSettings.testnet; diff --git a/wizard/WizardPassword.qml b/wizard/WizardPassword.qml index 2444add0..3ce6584d 100644 --- a/wizard/WizardPassword.qml +++ b/wizard/WizardPassword.qml @@ -64,7 +64,6 @@ ColumnLayout { function onPageClosed(settingsObject) { // TODO: set password on the final page - // settingsObject.wallet.setPassword(passwordItem.password) settingsObject['wallet_password'] = passwordUI.password return true } @@ -82,8 +81,8 @@ ColumnLayout { ListModel { id: dotsModel ListElement { dotColor: "#36B05B" } - ListElement { dotColor: "#36B05B" } - //ListElement { dotColor: "#FFE00A" } + ListElement { dotColor: "#FFE00A" } + ListElement { dotColor: "#DBDBDB" } ListElement { dotColor: "#DBDBDB" } } @@ -107,7 +106,7 @@ ColumnLayout { Layout.fillWidth: true id: titleText font.family: "Arial" - font.pixelSize: 28 + font.pixelSize: 28 * scaleRatio wrapMode: Text.Wrap horizontalAlignment: Text.AlignHCenter //renderType: Text.NativeRendering @@ -117,9 +116,9 @@ ColumnLayout { Text { Layout.fillWidth: true - Layout.bottomMargin: 30 + Layout.bottomMargin: 30 * scaleRatio font.family: "Arial" - font.pixelSize: 18 + font.pixelSize: 18 * scaleRatio wrapMode: Text.Wrap //renderType: Text.NativeRendering color: "#4A4646" diff --git a/wizard/WizardPasswordInput.qml b/wizard/WizardPasswordInput.qml index 6be2f618..a2651440 100644 --- a/wizard/WizardPasswordInput.qml +++ b/wizard/WizardPasswordInput.qml @@ -42,7 +42,7 @@ ColumnLayout { id : password focus:true font.family: "Arial" - font.pixelSize: (isMobile) ? 25 : 26 + font.pixelSize: (isMobile) ? 25 * scaleRatio : 26 * scaleRatio echoMode: TextInput.Password style: TextFieldStyle { renderType: Text.NativeRendering diff --git a/wizard/WizardPasswordUI.qml b/wizard/WizardPasswordUI.qml index c8e98905..bca911b8 100644 --- a/wizard/WizardPasswordUI.qml +++ b/wizard/WizardPasswordUI.qml @@ -42,19 +42,22 @@ ColumnLayout { wizard.nextButton.enabled = passwordItem.password === retypePasswordItem.password - // scorePassword returns value from 0 to... lots - var strength = walletManager.getPasswordStrength(passwordItem.password); - // consider anything below 10 bits as dire - strength -= 10 - if (strength < 0) - strength = 0 - // use a slight parabola to discourage short passwords - strength = strength ^ 1.2 / 3 - // mapScope does not clamp - if (strength > 100) - strength = 100 - // privacyLevel component uses 1..13 scale - privacyLevel.fillLevel = Utils.mapScope(1, 100, 1, 13, strength) + // TODO: password strength meter segfaults on Android. + if (!isAndroid) { + // scorePassword returns value from 0 to... lots + var strength = walletManager.getPasswordStrength(passwordItem.password); + // consider anything below 10 bits as dire + strength -= 10 + if (strength < 0) + strength = 0 + // use a slight parabola to discourage short passwords + strength = strength ^ 1.2 / 3 + // mapScope does not clamp + if (strength > 100) + strength = 100 + // privacyLevel component uses 1..13 scale + privacyLevel.fillLevel = Utils.mapScope(1, 100, 1, 13, strength) + } } function resetFocus() { @@ -64,8 +67,8 @@ ColumnLayout { WizardPasswordInput { id: passwordItem Layout.fillWidth: true - Layout.maximumWidth: 300 - Layout.minimumWidth: 200 + Layout.maximumWidth: 300 * scaleRatio + Layout.minimumWidth: 200 * scaleRatio Layout.alignment: Qt.AlignHCenter placeholderText : qsTr("Password") + translationManager.emptyString; KeyNavigation.tab: retypePasswordItem @@ -76,8 +79,8 @@ ColumnLayout { WizardPasswordInput { id: retypePasswordItem Layout.fillWidth: true - Layout.maximumWidth: 300 - Layout.minimumWidth: 200 + Layout.maximumWidth: 300 * scaleRatio + Layout.minimumWidth: 200 * scaleRatio Layout.alignment: Qt.AlignHCenter placeholderText : qsTr("Confirm password") + translationManager.emptyString; KeyNavigation.tab: passwordItem @@ -85,7 +88,8 @@ ColumnLayout { } PrivacyLevelSmall { - Layout.topMargin: isMobile ? 20 : 40 + visible: !isAndroid //TODO: strength meter doesnt work on Android + Layout.topMargin: isAndroid ? 20 * scaleRatio : 40 * scaleRatio Layout.fillWidth: true id: privacyLevel background: "#F0EEEE" diff --git a/wizard/WizardRecoveryWallet.qml b/wizard/WizardRecoveryWallet.qml index 240641e0..acf0caea 100644 --- a/wizard/WizardRecoveryWallet.qml +++ b/wizard/WizardRecoveryWallet.qml @@ -81,6 +81,12 @@ ColumnLayout { var tmp_wallet_filename = oshelper.temporaryFilename() console.log("Creating temporary wallet", tmp_wallet_filename) + // delete the temporary wallet object before creating new + if (typeof m_wallet !== 'undefined') { + walletManager.closeWallet() + console.log("deleting temporary wallet") + } + // From seed or keys if(fromSeed) var wallet = walletManager.recoveryWallet(tmp_wallet_filename, settingsObject.words, testnet, restoreHeight) @@ -92,7 +98,7 @@ ColumnLayout { var success = wallet.status === Wallet.Status_Ok; if (success) { - settingsObject['wallet'] = wallet; + m_wallet = wallet; settingsObject['is_recovering'] = true; settingsObject['tmp_wallet_filename'] = tmp_wallet_filename } else { diff --git a/wizard/WizardWelcome.qml b/wizard/WizardWelcome.qml index 8b440092..32249cb1 100644 --- a/wizard/WizardWelcome.qml +++ b/wizard/WizardWelcome.qml @@ -60,13 +60,13 @@ ColumnLayout { id: headerColumn Layout.leftMargin: wizardLeftMargin Layout.rightMargin: wizardRightMargin - Layout.bottomMargin: 40 - spacing: 20 + Layout.bottomMargin: 40 * scaleRatio + spacing: 20 * scaleRatio Text { Layout.fillWidth: true font.family: "Arial" - font.pixelSize: 28 + font.pixelSize: 28 * scaleRatio color: "#3F3F3F" wrapMode: Text.Wrap horizontalAlignment: Text.AlignHCenter @@ -76,7 +76,7 @@ ColumnLayout { Text { Layout.fillWidth: true font.family: "Arial" - font.pixelSize: 18 + font.pixelSize: 18 * scaleRatio color: "#4A4646" wrapMode: Text.Wrap horizontalAlignment: Text.AlignHCenter @@ -116,17 +116,16 @@ ColumnLayout { property int margin: (isMobile) ? 0 : Math.floor(appWindow.width/12); id: gridView - cellWidth: 140 - cellHeight: 120 + cellWidth: 140 * scaleRatio + cellHeight: 120 * scaleRatio model: languagesModel // Hack to center the flag grid - property int columns: Math.floor(appWindow.width/140) - Layout.leftMargin: margin + (appWindow.width - cellWidth*columns) /2 + property int columns: Math.floor(appWindow.width/cellWidth) + Layout.leftMargin: margin + (appWindow.width - cellWidth*columns) /2 Layout.rightMargin: margin Layout.fillWidth: true Layout.fillHeight: true - clip: true delegate: ColumnLayout { @@ -136,9 +135,9 @@ ColumnLayout { // Layout.alignment: Qt.AlignHCenter Rectangle { id: flagRect - width: 60; height: 60 + width: 60 * scaleRatio; height: 60 * scaleRatio // anchors.centerIn: parent - radius: 30 + radius: 30 * scaleRatio Layout.alignment: Qt.AlignHCenter color: gridView.currentIndex === index ? "#DBDBDB" : "#FFFFFF" Image { @@ -149,7 +148,7 @@ ColumnLayout { Text { font.family: "Arial" - font.pixelSize: 18 + font.pixelSize: 18 * scaleRatio // anchors.horizontalCenter: parent.horizontalCenter font.bold: gridView.currentIndex === index // elide: Text.ElideRight