Merge pull request

b1454c6 removed duplicated-by-mistake code (Ilya Kitaev)
be135e3 Processing splash formatting improved (Ilya Kitaev)
54b22d2 Wizard: finish page: restore height hidden if not set (Ilya Kitaev)
d67071a Settings page: layout items vertically (Ilya Kitaev)
4f4cc9c Settings page: small renaming and formatting (Ilya Kitaev)
da552a0 History: Fixed filter collapse (Ilya Kitaev)
7ed623e Basic mode: updating balance properly (Ilya Kitaev)
eafcf71 Minimized aka basic mode (Ilya Kitaev)
a032c84 History: fee color (Ilya Kitaev)
60b2d90 History: display "fee" for sent transactions (Ilya Kitaev)
0ac27e1 History: filter by amount (Ilya Kitaev)
10c2786 added restore height to wizardFinish (Jacob Brydolf)
883762c removed daemon-blockchain-progress rests (Jacob Brydolf)
38d9034 Wizard: added restore-height (Jacob Brydolf)
7f0d6c4 persistentSettings: added restoreHeight (Jacob Brydolf)
9f336a5 libwalletqt: added restore-height parameter to recoveryWallet(); (Jacob Brydolf)
0e0b0be removed moneroComponets dependency (Jacob Brydolf)
38db5a4 Integrate splash counter with restore-height (Jacob Brydolf)
868610c fix conflict (Jacob Brydolf)
4901839 added comma/locale separation to sync counter (Jacob Brydolf)
8b748bf History: filter by amount (Ilya Kitaev)
9b09e83 History: filtering by paymentId and date (Ilya Kitaev)
e3cea85 TransactionHistory: firstDateTime and lastDateTime properties (Ilya Kitaev)
9927182 Fix: unused dummy property removed (Ilya Kitaev)
fd9ed56 Fix: DatePicker component doesn't update the date (Ilya Kitaev)
612c497 TransactionHistory sorting (Ilya Kitaev)
e7e6c58 Adding sort-filter-proxy model (Ilya Kitaev)
0498c3b Transaction history is not crashing and refreshing properly (Ilya Kitaev)
defc2a4 build: get_libwallet_api.sh accepts build type ("Debug" or "Release", release is default); extra build script that skips cmake invocations (Ilya Kitaev)
2966b0d settings page: daemon change + password prompt on seed view (Jacob Brydolf)
9700944 close wallet before reopen (Jacob Brydolf)
522b067 Simple transaction history (Ilya Kitaev)
9cc92bf added onPageCompleted (Jacob Brydolf)
53d3bc4 Receive page: display long address from the beginning in TextField (Ilya Kitaev)
5814c19 Fixed overlapping clipboard icons on recieve page (Jacob Brydolf)
cd1247c Added option to show seed on settings-page (Jacob Brydolf)
af70c81 Added daemon BC sync progress bar depends on https://github.com/monero-project/monero/pull/1173 (Jacob Brydolf)
6a50951 Removed hardcoded height from PasswordDialog (Ilya Kitaev)
819be91 Integrating TransactionHistoryModel (Ilya Kitaev)
56c3579 Replaced Loader with StackView; Fixed payment id generation (Ilya Kitaev)
0ff3fd3 added TransactionHistoryModel; renamings (Ilya Kitaev)
bd8646d UX improvements in wizard - password page styling - tab navigation / focus on load - click language loads next page (Jacob Brydolf)
This commit is contained in:
Riccardo Spagni 2016-10-10 15:29:56 +02:00
commit 053e7c7cda
No known key found for this signature in database
GPG key ID: 55432DF31CCD4FCD
42 changed files with 1705 additions and 262 deletions

View file

@ -31,6 +31,8 @@ import QtGraphicalEffects 1.0
import "components"
import "pages"
// mbg033 @ 2016-10-08: Not used anymore, to be deleted
Rectangle {
id: root
width: 470

View file

@ -36,6 +36,7 @@ Rectangle {
property alias unlockedBalanceText: unlockedBalanceText.text
property alias balanceText: balanceText.text
property alias networkStatus : networkStatus
property alias daemonProgress : daemonProgress
signal dashboardClicked()
signal historyClicked()
@ -252,17 +253,17 @@ Rectangle {
panel.receiveClicked()
}
}
/*
Rectangle {
anchors.left: parent.left
anchors.right: parent.right
anchors.leftMargin: 16
color: transferButton.checked || historyButton.checked ? "#1C1C1C" : "#505050"
height: 1
}*/
}
// ------------- History tab ---------------
/*
MenuButton {
id: historyButton
anchors.left: parent.left
@ -276,7 +277,7 @@ Rectangle {
panel.historyClicked()
}
}
/*
Rectangle {
anchors.left: parent.left
anchors.right: parent.right
@ -330,7 +331,7 @@ Rectangle {
color: miningButton.checked || settingsButton.checked ? "#1C1C1C" : "#505050"
height: 1
}
*/
// ------------- Settings tab ---------------
MenuButton {
id: settingsButton
@ -345,16 +346,23 @@ Rectangle {
panel.settingsClicked()
}
}
*/
}
NetworkStatusItem {
id: networkStatus
anchors.left: parent.left
anchors.right: parent.right
anchors.bottom: parent.bottom
anchors.bottom: (daemonProgress.visible)? daemonProgress.top : parent.bottom;
connected: false
}
DaemonProgress {
id: daemonProgress
anchors.left: parent.left
anchors.right: parent.right
anchors.bottom: parent.bottom
}
}
// indicate disabled state
Desaturate {

View file

@ -27,44 +27,103 @@
// THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
import QtQuick 2.2
import QtQml 2.0
import QtQuick.Controls 2.0
import QtQuick.Layouts 1.1
import QtGraphicalEffects 1.0
import "pages"
Rectangle {
id: root
color: "#F0EEEE"
property Item currentView
property bool basicMode : false
property string balanceText
property string unlockedBalanceText
property Transfer transferView: Transfer { }
property Receive receiveView: Receive { }
property History historyView: History { }
property Settings settingsView: Settings { }
signal paymentClicked(string address, string paymentId, double amount, int mixinCount, int priority)
signal generatePaymentIdInvoked()
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" }
}
]
color: "#F0EEEE"
onCurrentViewChanged: {
if (currentView) {
stackView.replace(currentView)
// Component.onCompleted is called before wallet is initilized
if (typeof currentView.onPageCompleted === "function") {
currentView.onPageCompleted();
}
}
}
// 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"
PropertyChanges { }
}, State {
name: "History"
PropertyChanges { target: root; currentView: historyView }
PropertyChanges { target: historyView; model: appWindow.currentWallet.historyModel }
}, State {
name: "Transfer"
PropertyChanges { target: root; currentView: transferView }
}, State {
name: "Receive"
PropertyChanges { target: root; currentView: receiveView }
}, State {
name: "AddressBook"
PropertyChanges { /*TODO*/ }
}, State {
name: "Settings"
PropertyChanges { target: root; currentView: settingsView }
}, State {
name: "Mining"
PropertyChanges { /*TODO*/ }
}
]
// color stripe at the top
Row {
id: styledRow
height: 4
anchors.top: parent.top
anchors.left: parent.left
anchors.right: parent.right
anchors.top: parent.top
Rectangle { height: 4; width: parent.width / 5; color: "#FFE00A" }
Rectangle { height: 4; width: parent.width / 5; color: "#6B0072" }
@ -73,28 +132,127 @@ Rectangle {
Rectangle { height: 4; width: parent.width / 5; color: "#FF4F41" }
}
Loader {
id: loader
anchors.left: parent.left
anchors.right: parent.right
anchors.top: styledRow.bottom
anchors.bottom: parent.bottom
onLoaded: {
console.log("Loaded " + item);
ColumnLayout {
anchors.fill: parent
anchors.margins: 2
anchors.topMargin: 30
spacing: 0
// BasicPanel header
Rectangle {
id: header
anchors.leftMargin: 1
anchors.rightMargin: 1
Layout.fillWidth: true
Layout.preferredHeight: 64
color: "#FFFFFF"
visible: basicMode
Image {
id: logo
anchors.verticalCenter: parent.verticalCenter
anchors.verticalCenterOffset: -5
anchors.left: parent.left
anchors.leftMargin: 20
source: "images/moneroLogo2.png"
}
Grid {
anchors.verticalCenter: parent.verticalCenter
anchors.right: parent.right
width: 256
columns: 3
Text {
width: 116
height: 20
font.family: "Arial"
font.pixelSize: 12
font.letterSpacing: -1
elide: Text.ElideRight
horizontalAlignment: Text.AlignLeft
verticalAlignment: Text.AlignBottom
color: "#535353"
text: qsTr("Balance:")
}
Text {
id: balanceText
width: 110
height: 20
font.family: "Arial"
font.pixelSize: 18
font.letterSpacing: -1
elide: Text.ElideRight
horizontalAlignment: Text.AlignLeft
verticalAlignment: Text.AlignBottom
color: "#000000"
text: root.balanceText
}
Item {
height: 20
width: 20
Image {
anchors.verticalCenter: parent.verticalCenter
anchors.left: parent.left
source: "images/lockIcon.png"
}
}
Text {
width: 116
height: 20
font.family: "Arial"
font.pixelSize: 12
font.letterSpacing: -1
elide: Text.ElideRight
horizontalAlignment: Text.AlignLeft
verticalAlignment: Text.AlignBottom
color: "#535353"
text: qsTr("Unlocked Balance:")
}
Text {
id: availableBalanceText
width: 110
height: 20
font.family: "Arial"
font.pixelSize: 14
font.letterSpacing: -1
elide: Text.ElideRight
horizontalAlignment: Text.AlignLeft
verticalAlignment: Text.AlignBottom
color: "#000000"
text: root.unlockedBalanceText
}
}
Rectangle {
anchors.left: parent.left
anchors.right: parent.right
anchors.bottom: parent.bottom
height: 1
color: "#DBDBDB"
}
}
}
/* connect "payment" click */
Connections {
ignoreUnknownSignals: false
target: loader.item
onPaymentClicked : {
console.log("MiddlePanel: paymentClicked")
paymentClicked(address, paymentId, amount, mixinCount, priority)
// Views container
StackView {
id: stackView
initialItem: transferView
anchors.topMargin: 30
Layout.fillWidth: true
Layout.fillHeight: true
anchors.top: styledRow.bottom
anchors.margins: 4
clip: true // otherwise animation will affect left panel
}
}
// border
Rectangle {
anchors.top: styledRow.bottom
anchors.bottom: parent.bottom
@ -117,12 +275,25 @@ Rectangle {
anchors.bottom: parent.bottom
height: 1
color: "#DBDBDB"
}
// indicate disabled state
// indicates disabled state
Desaturate {
anchors.fill: parent
source: parent
desaturation: root.enabled ? 0.0 : 1.0
}
/* connect "payment" click */
Connections {
ignoreUnknownSignals: false
target: transferView
onPaymentClicked : {
console.log("MiddlePanel: paymentClicked")
paymentClicked(address, paymentId, amount, mixinCount, priority)
}
}
}

52
build_libwallet_api.sh Executable file
View file

@ -0,0 +1,52 @@
#!/bin/bash
# MONERO_URL=https://github.com/monero-project/monero.git
# MONERO_BRANCH=master
CPU_CORE_COUNT=$(grep -c ^processor /proc/cpuinfo 2>/dev/null || sysctl -n hw.ncpu)
pushd $(pwd)
ROOT_DIR="$( cd "$( dirname "${BASH_SOURCE[0]}" )" && pwd )"
source $ROOT_DIR/utils.sh
INSTALL_DIR=$ROOT_DIR/wallet
MONERO_DIR=$ROOT_DIR/monero
mkdir -p $MONERO_DIR/build/release
pushd $MONERO_DIR/build/release
# reusing function from "utils.sh"
platform=$(get_platform)
pushd $MONERO_DIR/build/release/src/wallet
make -j$CPU_CORE_COUNT
make install -j$CPU_CORE_COUNT
popd
# unbound is one more dependency. can't be merged to the wallet_merged
# since filename conflict (random.c.obj)
# for Linux, we use libunbound shipped with the system, so we don't need to build it
if [ "$platform" != "linux" ]; then
echo "Building libunbound..."
pushd $MONERO_DIR/build/release/external/unbound
# no need to make, it was already built as dependency for libwallet
# make -j$CPU_CORE_COUNT
make install -j$CPU_CORE_COUNT
popd
fi
popd

View file

@ -27,7 +27,7 @@
// THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
import QtQuick 2.0
import moneroComponents 1.0
import moneroComponents.Clipboard 1.0
ListView {
id: listView

View file

@ -0,0 +1,90 @@
// Copyright (c) 2014-2015, 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
Item {
id: item
property int fillLevel: 0
height: 44
anchors.margins: 10
visible: false
//clip: true
function updateProgress(currentBlock,targetBlock){
if(targetBlock > 0) {
var progressLevel = ((currentBlock/targetBlock) * 100).toFixed(0);
fillLevel = progressLevel
console.log("target block: ",progressLevel)
progressText.text = qsTr("Synchronizing blocks %1/%2").arg(currentBlock.toFixed(0)).arg(targetBlock.toFixed(0));
console.log("Progress text: " + progressText.text);
// TODO: lower daemon block height cache, ttl and refresh interval?
item.visible = (currentBlock < targetBlock)
}
}
Rectangle {
id: bar
anchors.left: parent.left
anchors.right: parent.right
anchors.top: parent.top
height: 18
//radius: 4
color: "#FFFFFF"
Rectangle {
id: fillRect
anchors.top: parent.top
anchors.bottom: parent.bottom
anchors.left: parent.left
anchors.margins: 2
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"
}
}
}
Text {
id:progressText
anchors.bottom: parent.bottom
font.family: "Arial"
font.pixelSize: 12
color: "#545454"
text: qsTr("Synchronizing blocks")
}
}

View file

@ -27,7 +27,7 @@
// THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
import QtQuick 2.0
import moneroComponents 1.0
import moneroComponents.Clipboard 1.0
ListView {
id: listView

View file

@ -33,12 +33,13 @@ import QtQuick.Controls.Styles 1.2
Item {
id: datePicker
property bool expanded: false
property var currentDate: new Date()
property date currentDate
property bool showCurrentDate: true
height: 37
width: 156
onExpandedChanged: if(expanded) appWindow.currentItem = datePicker
function hide() { datePicker.expanded = false }
function containsPoint(px, py) {
if(px < 0)
@ -121,12 +122,29 @@ Item {
}
Row {
id: dateInput
anchors.verticalCenter: parent.verticalCenter
anchors.left: parent.left
anchors.leftMargin: 10
function setDate(date) {
var day = date.getDate()
var month = date.getMonth() + 1
dayInput.text = day < 10 ? "0" + day : day
monthInput.text = month < 10 ? "0" + month : month
yearInput.text = date.getFullYear()
}
Connections {
target: datePicker
onCurrentDateChanged: {
dateInput.setDate(datePicker.currentDate)
}
}
TextInput {
id: dayInput
readOnly: true
width: 22
font.family: "Arial"
font.pixelSize: 18
@ -135,6 +153,7 @@ Item {
horizontalAlignment: TextInput.AlignHCenter
validator: IntValidator{bottom: 01; top: 31;}
KeyNavigation.tab: monthInput
text: {
if(datePicker.showCurrentDate) {
var day = datePicker.currentDate.getDate()
@ -158,6 +177,7 @@ Item {
TextInput {
id: monthInput
readOnly: true
width: 22
font.family: "Arial"
font.pixelSize: 18
@ -277,12 +297,7 @@ Item {
anchors.fill: parent
onClicked: {
if(styleData.visibleMonth) {
var date = styleData.date
var day = date.getDate()
var month = date.getMonth() + 1
dayInput.text = day < 10 ? "0" + day : day
monthInput.text = month < 10 ? "0" + month : month
yearInput.text = date.getFullYear()
currentDate = styleData.date
datePicker.expanded = false
} else {
var date = styleData.date

View file

@ -27,12 +27,14 @@
// THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
import QtQuick 2.0
import moneroComponents 1.0
import moneroComponents.Clipboard 1.0
ListView {
id: listView
clip: true
boundsBehavior: ListView.StopAtBounds
property var previousItem
footer: Rectangle {
height: 127
@ -48,7 +50,7 @@ ListView {
}
}
property var previousItem
delegate: Rectangle {
id: delegate
height: 114
@ -63,13 +65,13 @@ ListView {
anchors.right: parent.right
anchors.top: parent.top
anchors.topMargin: 14
// -- direction indicator
Rectangle {
id: dot
width: 14
height: width
radius: width / 2
color: out ? "#FF4F41" : "#36B05B"
color: isOut ? "#FF4F41" : "#36B05B"
}
Item { //separator
@ -77,6 +79,8 @@ ListView {
height: 14
}
// -- description aka recepient name from address book (TODO)
/*
Text {
id: descriptionText
width: text.length ? (descriptionArea.containsMouse ? parent.width - x - 12 : 120) : 0
@ -94,13 +98,15 @@ ListView {
hoverEnabled: true
}
}
*/
/*
Item { //separator
width: descriptionText.width ? 12 : 0
height: 14
visible: !descriptionArea.containsMouse
}
*/
// -- address (in case outgoing transaction) - N/A in case of incoming
Text {
id: addressText
anchors.verticalCenter: dot.verticalCenter
@ -109,11 +115,11 @@ ListView {
font.family: "Arial"
font.pixelSize: 14
color: "#545454"
text: address
visible: !descriptionArea.containsMouse
text: hash
// visible: !descriptionArea.containsMouse
}
}
// -- "PaymentID" title
Text {
id: paymentLabel
anchors.left: parent.left
@ -128,7 +134,7 @@ ListView {
color: "#535353"
text: paymentId !== "" ? qsTr("Payment ID:") + translationManager.emptyString : ""
}
// -- "PaymentID" value
Text {
anchors.bottom: paymentLabel.bottom
anchors.left: paymentLabel.right
@ -143,7 +149,7 @@ ListView {
color: "#545454"
text: paymentId
}
// -- "Date", "Balance" and "Amound" section
Row {
anchors.left: parent.left
anchors.bottom: parent.bottom
@ -155,6 +161,7 @@ ListView {
height: 14
}
// -- "Date" column
Column {
anchors.top: parent.top
width: 215
@ -189,10 +196,13 @@ ListView {
}
}
}
// -- "Balance" column
// XXX: we don't have a balance
/*
Column {
anchors.top: parent.top
width: 148
visible: false
Text {
anchors.left: parent.left
@ -210,7 +220,9 @@ ListView {
text: balance
}
}
*/
// -- "Amount column
Column {
anchors.top: parent.top
width: 148
@ -230,8 +242,8 @@ ListView {
anchors.bottomMargin: 3
font.family: "Arial"
font.pixelSize: 16
color: out ? "#FF4F41" : "#36B05B"
text: out ? "↓" : "↑"
color: isOut ? "#FF4F41" : "#36B05B"
text: isOut ? "↓" : "↑"
}
Text {
@ -239,22 +251,44 @@ ListView {
font.family: "Arial"
font.pixelSize: 18
font.letterSpacing: -1
color: out ? "#FF4F41" : "#36B05B"
text: amount
color: isOut ? "#FF4F41" : "#36B05B"
text: displayAmount
}
}
}
// -- "Fee column
Column {
anchors.top: parent.top
width: 148
visible: isOut
Text {
anchors.left: parent.left
font.family: "Arial"
font.pixelSize: 12
color: "#545454"
text: qsTr("Fee") + translationManager.emptyString
}
Row {
spacing: 2
Text {
anchors.bottom: parent.bottom
font.family: "Arial"
font.pixelSize: 18
font.letterSpacing: -1
color: "#FF4F41"
text: fee
}
}
}
}
ListModel {
id: dropModel
ListElement { name: "<b>Copy address to clipboard</b>"; icon: "../images/dropdownCopy.png" }
ListElement { name: "<b>Add to address book</b>"; icon: "../images/dropdownAdd.png" }
ListElement { name: "<b>Send to same destination</b>"; icon: "../images/dropdownSend.png" }
ListElement { name: "<b>Find similar transactions</b>"; icon: "../images/dropdownSearch.png" }
}
Clipboard { id: clipboard }
/*
// Transaction dropdown menu.
// Disable for now until AddressBook implemented
TableDropdown {
id: dropdown
anchors.right: parent.right
@ -282,5 +316,16 @@ ListView {
height: 1
color: "#DBDBDB"
}
*/
}
ListModel {
id: dropModel
ListElement { name: "<b>Copy address to clipboard</b>"; icon: "../images/dropdownCopy.png" }
ListElement { name: "<b>Add to address book</b>"; icon: "../images/dropdownAdd.png" }
ListElement { name: "<b>Send to same destination</b>"; icon: "../images/dropdownSend.png" }
ListElement { name: "<b>Find similar transactions</b>"; icon: "../images/dropdownSearch.png" }
}
Clipboard { id: clipboard }
}

View file

@ -33,6 +33,7 @@ Item {
property alias text: input.text
property alias validator: input.validator
property alias readOnly : input.readOnly
property alias cursorPosition: input.cursorPosition
property int fontSize: 18
@ -56,7 +57,7 @@ Item {
id: input
anchors.fill: parent
anchors.leftMargin: 4
anchors.rightMargin: 4
anchors.rightMargin: 30
font.pixelSize: parent.fontSize
}
}

View file

@ -40,7 +40,6 @@ Dialog {
standardButtons: StandardButton.Ok + StandardButton.Cancel
ColumnLayout {
id: column
height: 40
anchors.fill: parent
Label {

View file

@ -43,8 +43,13 @@ Window {
opacity: 0.7
ColumnLayout {
id: rootLayout
anchors.horizontalCenter: parent.horizontalCenter
anchors.verticalCenter: parent.verticalCenter
anchors.left: parent.left
anchors.right: parent.right
anchors.leftMargin: 30
anchors.rightMargin: 30
BusyIndicator {
running: parent.visible
@ -59,6 +64,7 @@ Window {
}
horizontalAlignment: Text.AlignHCenter
Layout.alignment: Qt.AlignVCenter | Qt.AlignHCenter
Layout.fillWidth: true
}
@ -69,6 +75,7 @@ Window {
}
horizontalAlignment: Text.AlignHCenter
Layout.alignment: Qt.AlignVCenter | Qt.AlignHCenter
Layout.fillWidth: true
}
}
}

View file

@ -5,6 +5,11 @@ MONERO_URL=https://github.com/monero-project/monero.git
MONERO_BRANCH=master
# MONERO_URL=https://github.com/mbg033/monero.git
# MONERO_BRANCH=develop
# Buidling "debug" build optionally
BUILD_TYPE=$1
if [ -z $BUILD_TYPE ]; then
BUILD_TYPE=Release
fi
# thanks to SO: http://stackoverflow.com/a/20283965/4118915
CPU_CORE_COUNT=$(grep -c ^processor /proc/cpuinfo 2>/dev/null || sysctl -n hw.ncpu)
pushd $(pwd)
@ -39,19 +44,19 @@ platform=$(get_platform)
if [ "$platform" == "darwin" ]; then
# Do something under Mac OS X platform
echo "Configuring build for MacOS.."
cmake -D CMAKE_BUILD_TYPE=Release -D STATIC=ON -D BUILD_GUI_DEPS=ON -D INSTALL_VENDORED_LIBUNBOUND=ON -D CMAKE_INSTALL_PREFIX="$MONERO_DIR" ../..
cmake -D CMAKE_BUILD_TYPE=$BUILD_TYPE -D STATIC=ON -D BUILD_GUI_DEPS=ON -D INSTALL_VENDORED_LIBUNBOUND=ON -D CMAKE_INSTALL_PREFIX="$MONERO_DIR" ../..
elif [ "$platform" == "linux" ]; then
# Do something under GNU/Linux platform
echo "Configuring build for Linux.."
cmake -D CMAKE_BUILD_TYPE=Release -D STATIC=ON -D BUILD_GUI_DEPS=ON -D CMAKE_INSTALL_PREFIX="$MONERO_DIR" ../..
cmake -D CMAKE_BUILD_TYPE=$BUILD_TYPE -D STATIC=ON -D BUILD_GUI_DEPS=ON -D CMAKE_INSTALL_PREFIX="$MONERO_DIR" ../..
elif [ "$platform" == "mingw64" ]; then
# Do something under Windows NT platform
echo "Configuring build for MINGW64.."
cmake -D CMAKE_BUILD_TYPE=Release -D STATIC=ON -D BUILD_GUI_DEPS=ON -D INSTALL_VENDORED_LIBUNBOUND=ON -D CMAKE_INSTALL_PREFIX="$MONERO_DIR" -G "MSYS Makefiles" ../..
cmake -D CMAKE_BUILD_TYPE=$BUILD_TYPE -D STATIC=ON -D BUILD_GUI_DEPS=ON -D INSTALL_VENDORED_LIBUNBOUND=ON -D CMAKE_INSTALL_PREFIX="$MONERO_DIR" -G "MSYS Makefiles" ../..
elif [ "$platform" == "mingw32" ]; then
# Do something under Windows NT platform
echo "Configuring build for MINGW32.."
cmake -D CMAKE_BUILD_TYPE=Release -D STATIC=ON -D BUILD_GUI_DEPS=ON -D INSTALL_VENDORED_LIBUNBOUND=ON -D CMAKE_INSTALL_PREFIX="$MONERO_DIR" -G "MSYS Makefiles" ../..
cmake -D CMAKE_BUILD_TYPE=$BUILD_TYPE -D STATIC=ON -D BUILD_GUI_DEPS=ON -D INSTALL_VENDORED_LIBUNBOUND=ON -D CMAKE_INSTALL_PREFIX="$MONERO_DIR" -G "MSYS Makefiles" ../..
else
echo "Unsupported platform: $platform"
popd

View file

@ -39,8 +39,10 @@
#include "Wallet.h"
#include "PendingTransaction.h"
#include "TranslationManager.h"
#include "TransactionInfo.h"
#include "TransactionHistory.h"
#include "model/TransactionHistoryModel.h"
#include "model/TransactionHistorySortFilterModel.h"
int main(int argc, char *argv[])
@ -56,23 +58,38 @@ int main(int argc, char *argv[])
filter *eventFilter = new filter;
app.installEventFilter(eventFilter);
qmlRegisterType<clipboardAdapter>("moneroComponents", 1, 0, "Clipboard");
// registering types for QML
qmlRegisterType<clipboardAdapter>("moneroComponents.Clipboard", 1, 0, "Clipboard");
qmlRegisterUncreatableType<Wallet>("Bitmonero.Wallet", 1, 0, "Wallet", "Wallet can't be instantiated directly");
qmlRegisterUncreatableType<Wallet>("moneroComponents.Wallet", 1, 0, "Wallet", "Wallet can't be instantiated directly");
qmlRegisterUncreatableType<PendingTransaction>("Bitmonero.PendingTransaction", 1, 0, "PendingTransaction",
qmlRegisterUncreatableType<PendingTransaction>("moneroComponents.PendingTransaction", 1, 0, "PendingTransaction",
"PendingTransaction can't be instantiated directly");
qmlRegisterUncreatableType<WalletManager>("Bitmonero.WalletManager", 1, 0, "WalletManager",
qmlRegisterUncreatableType<WalletManager>("moneroComponents.WalletManager", 1, 0, "WalletManager",
"WalletManager can't be instantiated directly");
qmlRegisterUncreatableType<TranslationManager>("moneroComponents", 1, 0, "TranslationManager",
qmlRegisterUncreatableType<TranslationManager>("moneroComponents.TranslationManager", 1, 0, "TranslationManager",
"TranslationManager can't be instantiated directly");
qmlRegisterUncreatableType<TransactionHistoryModel>("moneroComponents.TransactionHistoryModel", 1, 0, "TransactionHistoryModel",
"TransactionHistoryModel can't be instantiated directly");
qmlRegisterUncreatableType<TransactionHistorySortFilterModel>("moneroComponents.TransactionHistorySortFilterModel", 1, 0, "TransactionHistorySortFilterModel",
"TransactionHistorySortFilterModel can't be instantiated directly");
qmlRegisterUncreatableType<TransactionHistory>("moneroComponents.TransactionHistory", 1, 0, "TransactionHistory",
"TransactionHistory can't be instantiated directly");
qmlRegisterUncreatableType<TransactionInfo>("moneroComponents.TransactionInfo", 1, 0, "TransactionInfo",
"TransactionHistory can't be instantiated directly");
qRegisterMetaType<PendingTransaction::Priority>();
qRegisterMetaType<TransactionInfo::Direction>();
qRegisterMetaType<TransactionHistoryModel::TransactionInfoRole>();
QQmlApplicationEngine engine;

114
main.qml
View file

@ -32,8 +32,9 @@ import QtQuick.Controls 1.1
import QtQuick.Controls.Styles 1.1
import QtQuick.Dialogs 1.2
import Qt.labs.settings 1.0
import Bitmonero.Wallet 1.0
import Bitmonero.PendingTransaction 1.0
import moneroComponents.Wallet 1.0
import moneroComponents.PendingTransaction 1.0
import "components"
@ -54,6 +55,10 @@ ApplicationWindow {
property alias password : passwordDialog.password
property int splashCounter: 0
property bool isNewWallet: false
property int restoreHeight:0
// true if wallet ever synchronized
property bool walletInitialized : false
function altKeyReleased() { ctrlPressed = false; }
@ -138,15 +143,31 @@ ApplicationWindow {
middlePanel.paymentClicked.connect(handlePayment);
// basicPanel.paymentClicked.connect(handlePayment);
// currentWallet is defined on daemon address change - close/reopen
if (currentWallet !== undefined) {
console.log("closing currentWallet")
walletManager.closeWallet(currentWallet);
}
// wallet already opened with wizard, we just need to initialize it
if (typeof wizard.settings['wallet'] !== 'undefined') {
console.log("using wizard wallet")
//Set restoreHeight
if(persistentSettings.restoreHeight > 0){
restoreHeight = persistentSettings.restoreHeight
}
console.log("using wizard wallet")
connectWallet(wizard.settings['wallet'])
isNewWallet = true
// We don't need the wizard wallet any more - delete to avoid conflict with daemon adress change
delete wizard.settings['wallet']
} else {
var wallet_path = walletPath();
// console.log("opening wallet at: ", wallet_path, "with password: ", appWindow.password);
console.log("opening wallet at: ", wallet_path);
console.log("opening wallet at: ", wallet_path, ", testnet: ", persistentSettings.testnet);
walletManager.openWalletAsync(wallet_path, appWindow.password,
persistentSettings.testnet);
}
@ -159,11 +180,10 @@ ApplicationWindow {
currentWallet.refreshed.connect(onWalletRefresh)
currentWallet.updated.connect(onWalletUpdate)
currentWallet.newBlock.connect(onWalletNewBlock)
currentWallet.moneySpent.connect(onWalletMoneySent)
currentWallet.moneyReceived.connect(onWalletMoneyReceived)
console.log("initializing with daemon address: ", persistentSettings.daemon_address)
currentWallet.initAsync(persistentSettings.daemon_address, 0);
}
function walletPath() {
@ -210,9 +230,8 @@ ApplicationWindow {
function onWalletUpdate() {
console.log(">>> wallet updated")
basicPanel.unlockedBalanceText = leftPanel.unlockedBalanceText =
walletManager.displayAmount(currentWallet.unlockedBalance);
basicPanel.balanceText = leftPanel.balanceText = walletManager.displayAmount(currentWallet.balance);
middlePanel.unlockedBalanceText = leftPanel.unlockedBalanceText = walletManager.displayAmount(currentWallet.unlockedBalance);
middlePanel.balanceText = leftPanel.balanceText = walletManager.displayAmount(currentWallet.balance);
}
function onWalletRefresh() {
@ -220,6 +239,9 @@ ApplicationWindow {
if (splash.visible) {
hideProcessingSplash()
}
var dCurrentBlock = currentWallet.daemonBlockChainHeight();
var dTargetBlock = currentWallet.daemonBlockChainTargetHeight();
leftPanel.daemonProgress.updateProgress(dCurrentBlock,dTargetBlock);
// Store wallet after first refresh. To prevent broken wallet after a crash
// TODO: Move this to libwallet?
@ -229,22 +251,49 @@ ApplicationWindow {
console.log("wallet stored after first successfull refresh")
}
// initialize transaction history once wallet is initializef first time;
if (!walletInitialized) {
currentWallet.history.refresh()
walletInitialized = true
}
leftPanel.networkStatus.connected = currentWallet.connected
onWalletUpdate();
}
function onWalletNewBlock(blockHeight) {
if (splash.visible) {
var currHeight = blockHeight.toFixed(0)
if(currHeight > splashCounter + 1000){
var currHeight = blockHeight
//fast refresh until restoreHeight is reached
var increment = ((restoreHeight == 0) || currHeight < restoreHeight)? 1000 : 10
if(currHeight > splashCounter + increment){
splashCounter = currHeight
var progressText = qsTr("Synchronizing blocks %1/%2").arg(currHeight).arg(currentWallet.daemonBlockChainHeight().toFixed(0));
var locale = Qt.locale()
var currHeightString = currHeight.toLocaleString(locale,"f",0)
var targetHeightString = currentWallet.daemonBlockChainHeight().toLocaleString(locale,"f",0)
var progressText = qsTr("Synchronizing blocks %1 / %2").arg(currHeightString).arg(targetHeightString);
console.log("Progress text: " + progressText);
splash.heightProgressText = progressText
}
}
}
function onWalletMoneyReceived(txId, amount) {
// refresh transaction history here
currentWallet.refresh()
currentWallet.history.refresh() // this will refresh model
}
function onWalletMoneySent(txId, amount) {
// refresh transaction history here
currentWallet.refresh()
currentWallet.history.refresh() // this will refresh model
}
function walletsFound() {
var wallets = walletManager.findWallets(moneroAccountsDir);
@ -338,7 +387,7 @@ ApplicationWindow {
middlePanel.enabled = enable;
leftPanel.enabled = enable;
rightPanel.enabled = enable;
basicPanel.enabled = enable;
// basicPanel.enabled = enable;
}
function showProcessingSplash(message) {
@ -396,6 +445,7 @@ ApplicationWindow {
property bool testnet: true
property string daemon_address: "localhost:38081"
property string payment_id
property int restoreHeight:0
}
// TODO: replace with customized popups
@ -440,7 +490,7 @@ ApplicationWindow {
ProcessingSplash {
id: splash
width: appWindow.width / 2
width: appWindow.width / 1.5
height: appWindow.height / 2
x: (appWindow.width - width) / 2 + appWindow.x
y: (appWindow.height - height) / 2 + appWindow.y
@ -514,7 +564,7 @@ ApplicationWindow {
MiddlePanel {
id: middlePanel
anchors.bottom: parent.bottom
anchors.left: leftPanel.right
anchors.left: leftPanel.visible ? leftPanel.right : parent.left
anchors.right: rightPanel.left
height: parent.height
state: "Transfer"
@ -526,16 +576,6 @@ ApplicationWindow {
visible: false
}
BasicPanel {
id: basicPanel
x: 0
anchors.bottom: parent.bottom
anchors.top: parent.top
anchors.left: parent.left
anchors.right: parent.right
visible: false
}
MouseArea {
id: frameArea
property bool blocked: false
@ -591,25 +631,26 @@ ApplicationWindow {
duration: 200
}
PropertyAction {
targets: [leftPanel, middlePanel, rightPanel]
targets: [leftPanel, rightPanel]
properties: "visible"
value: false
}
PropertyAction {
target: basicPanel
properties: "visible"
target: middlePanel
properties: "basicMode"
value: true
}
NumberAnimation {
target: appWindow
properties: "height"
to: basicPanel.height
to: middlePanel.height
easing.type: Easing.InCubic
duration: 200
}
onStopped: {
middlePanel.visible = false
// middlePanel.visible = false
rightPanel.visible = false
leftPanel.visible = false
}
@ -625,8 +666,8 @@ ApplicationWindow {
duration: 200
}
PropertyAction {
target: basicPanel
properties: "visible"
target: middlePanel
properties: "basicMode"
value: false
}
PropertyAction {
@ -719,8 +760,13 @@ ApplicationWindow {
anchors.left: parent.left
anchors.right: parent.right
onGoToBasicVersion: {
if(yes) goToBasicAnimation.start()
else goToProAnimation.start()
if (yes) {
// basicPanel.currentView = middlePanel.currentView
goToBasicAnimation.start()
} else {
// middlePanel.currentView = basicPanel.currentView
goToProAnimation.start()
}
}
MouseArea {

View file

@ -10,7 +10,8 @@ CONFIG += c++11
QMAKE_DISTCLEAN += -r $$WALLET_ROOT
INCLUDEPATH += $$WALLET_ROOT/include \
$$PWD/src/libwalletqt
$$PWD/src/libwalletqt \
$$PWD/src
HEADERS += \
filter.h \
@ -22,7 +23,9 @@ HEADERS += \
src/libwalletqt/TransactionHistory.h \
src/libwalletqt/TransactionInfo.h \
oshelper.h \
TranslationManager.h
TranslationManager.h \
src/model/TransactionHistoryModel.h \
src/model/TransactionHistorySortFilterModel.h
SOURCES += main.cpp \
@ -35,7 +38,9 @@ SOURCES += main.cpp \
src/libwalletqt/TransactionHistory.cpp \
src/libwalletqt/TransactionInfo.cpp \
oshelper.cpp \
TranslationManager.cpp
TranslationManager.cpp \
src/model/TransactionHistoryModel.cpp \
src/model/TransactionHistorySortFilterModel.cpp
lupdate_only {
SOURCES = *.qml \

View file

@ -27,10 +27,28 @@
// THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
import QtQuick 2.0
import moneroComponents.Wallet 1.0
import moneroComponents.WalletManager 1.0
import moneroComponents.TransactionHistory 1.0
import moneroComponents.TransactionInfo 1.0
import moneroComponents.TransactionHistoryModel 1.0
import "../components"
Rectangle {
id: root
property var model
color: "#F0EEEE"
onModelChanged: {
if (typeof model !== 'undefined') {
// setup date filter scope according to real transactions
fromDatePicker.currentDate = model.transactionHistory.firstDateTime
toDatePicker.currentDate = model.transactionHistory.lastDateTime
}
}
Text {
id: filterHeaderText
@ -47,6 +65,8 @@ Rectangle {
text: qsTr("Filter transactions history") + translationManager.emptyString
}
// Filter by Address input (senseless, removing)
/*
Label {
id: addressLabel
anchors.left: parent.left
@ -67,11 +87,14 @@ Rectangle {
anchors.rightMargin: 17
anchors.topMargin: 5
}
*/
// Filter by Payment ID input
Label {
id: paymentIdLabel
anchors.left: parent.left
anchors.top: addressLine.bottom
anchors.top: filterHeaderText.bottom // addressLine.bottom
anchors.leftMargin: 17
anchors.topMargin: 17
text: qsTr("Payment ID <font size='2'>(Optional)</font>") + translationManager.emptyString
@ -84,12 +107,16 @@ Rectangle {
id: paymentIdLine
anchors.left: parent.left
anchors.right: parent.right
anchors.top: paymentIdLabel.bottom
anchors.top: paymentIdLabel.bottom // addressLabel.bottom
anchors.leftMargin: 17
anchors.rightMargin: 17
anchors.topMargin: 5
}
// Filter by description input (not implemented yet)
/*
Label {
id: descriptionLabel
anchors.left: parent.left
@ -110,11 +137,14 @@ Rectangle {
anchors.rightMargin: 17
anchors.topMargin: 5
}
*/
// DateFrom picker
Label {
id: dateFromText
anchors.left: parent.left
anchors.top: descriptionLine.bottom
anchors.top: paymentIdLine.bottom // descriptionLine.bottom
anchors.leftMargin: 17
anchors.topMargin: 17
width: 156
@ -132,10 +162,11 @@ Rectangle {
z: 2
}
// DateTo picker
Label {
id: dateToText
anchors.left: dateFromText.right
anchors.top: descriptionLine.bottom
anchors.top: paymentIdLine.bottom //descriptionLine.bottom
anchors.leftMargin: 17
anchors.topMargin: 17
text: qsTr("To")
@ -163,10 +194,30 @@ Rectangle {
shadowPressedColor: "#2D002F"
releasedColor: "#6B0072"
pressedColor: "#4D0051"
onClicked: {
// Apply filter here;
model.paymentIdFilter = paymentIdLine.text
model.dateFromFilter = fromDatePicker.currentDate
model.dateToFilter = toDatePicker.currentDate
if (advancedFilteringCheckBox.checked) {
if (amountFromLine.text.length) {
model.amountFromFilter = parseFloat(amountFromLine.text)
}
if (amountToLine.text.length) {
model.amountToFilter = parseFloat(amountToLine.text)
}
var directionFilter = transactionsModel.get(transactionTypeDropdown.currentIndex).value
console.log("Direction filter: " + directionFilter)
model.directionFilter = directionFilter
}
}
}
CheckBox {
id: checkBox
id: advancedFilteringCheckBox
text: qsTr("Advance filtering")
anchors.left: filterButton.right
anchors.bottom: filterButton.bottom
@ -193,9 +244,10 @@ Rectangle {
ListModel {
id: transactionsModel
ListElement { column1: "SENT"; column2: "" }
ListElement { column1: "RECIVE"; column2: "" }
ListElement { column1: "ON HOLD"; column2: "" }
ListElement { column1: "ALL"; column2: ""; value: TransactionInfo.Direction_Both }
ListElement { column1: "SENT"; column2: ""; value: TransactionInfo.Direction_Out }
ListElement { column1: "RECEIVED"; column2: ""; value: TransactionInfo.Direction_In }
}
StandardDropdown {
@ -274,8 +326,11 @@ Rectangle {
anchors.fill: parent
onClicked: {
parent.expanded = !parent.expanded
if(checkBox.checked) tableRect.height = Qt.binding(function(){ return parent.expanded ? tableRect.expandedHeight : tableRect.collapsedHeight })
else tableRect.height = Qt.binding(function(){ return parent.expanded ? tableRect.expandedHeight : tableRect.middleHeight })
if (advancedFilteringCheckBox.checked) {
tableRect.height = Qt.binding(function() { return parent.expanded ? tableRect.expandedHeight : tableRect.collapsedHeight })
} else {
tableRect.height = Qt.binding(function() { return parent.expanded ? tableRect.expandedHeight : tableRect.middleHeight })
}
}
}
}
@ -312,10 +367,11 @@ Rectangle {
ListModel {
id: columnsModel
ListElement { columnName: "Address"; columnWidth: 127 }
ListElement { columnName: "Payment ID"; columnWidth: 127 }
ListElement { columnName: "Date"; columnWidth: 100 }
ListElement { columnName: "Amount"; columnWidth: 148 }
ListElement { columnName: "Description"; columnWidth: 148 }
// ListElement { columnName: "Description"; columnWidth: 148 }
}
TableHeader {
@ -328,21 +384,24 @@ Rectangle {
anchors.rightMargin: 14
dataModel: columnsModel
offset: 20
onSortRequest: console.log("column: " + column + " desc: " + desc)
}
ListModel {
id: testModel
ListElement { paymentId: "faef56b9acf67a7dba75ec01f403497049d7cff111628edfe7b57278554dc798"; address: "faef56b9acf67a7dba75ec01f403497049d7cff111628edfe7b57278554dc798"; date: "Jan 12, 2014"; time: "12:23 <font size='2'>AM</font>"; amount: "0.<font size='2'>000709159241</font>"; balance: "19301.<font size='2'>870709159241</font>"; description: "Client from Australia"; out: false }
ListElement { paymentId: "faef56b9acf67a7dba75ec01f403497049d7cff111628edfe7b57278554dc798"; address: "faef56b9acf67a7dba75ec01f403497049d7cff111628edfe7b57278554dc798"; date: "Jan 12, 2014"; time: "12:23 <font size='2'>AM</font>"; amount: "0.<font size='2'>000709159241</font>"; balance: "19301.<font size='2'>870709159241</font>"; description: ""; out: true }
ListElement { paymentId: "faef56b9acf67a7dba75ec01f403497049d7cff111628edfe7b57278554dc798"; address: "faef56b9acf67a7dba75ec01f403497049d7cff111628edfe7b57278554dc798"; date: "Jan 12, 2014"; time: "12:23 <font size='2'>AM</font>"; amount: "0.<font size='2'>000709159241</font>"; balance: "19301.<font size='2'>870709159241</font>"; description: ""; out: true }
ListElement { paymentId: ""; address: "faef56b9acf67a7dba75ec01f403497049d7cff111628edfe7b57278554dc798"; date: "Jan 12, 2014"; time: "12:23 <font size='2'>AM</font>"; amount: "0.<font size='2'>000709159241</font>"; balance: "19301.<font size='2'>870709159241</font>"; description: ""; out: false }
ListElement { paymentId: ""; address: "faef56b9acf67a7dba75ec01f403497049d7cff111628edfe7b57278554dc798"; date: "Jan 12, 2014"; time: "12:23 <font size='2'>AM</font>"; amount: "0.<font size='2'>000709159241</font>"; balance: "19301.<font size='2'>870709159241</font>"; description: "Client from Australia"; out: false }
ListElement { paymentId: "faef56b9acf67a7dba75ec01f403497049d7cff111628edfe7b57278554dc798"; address: "faef56b9acf67a7dba75ec01f403497049d7cff111628edfe7b57278554dc798"; date: "Jan 12, 2014"; time: "12:23 <font size='2'>AM</font>"; amount: "0.<font size='2'>000709159241</font>"; balance: "19301.<font size='2'>870709159241</font>"; description: ""; out: false }
ListElement { paymentId: ""; address: "faef56b9acf67a7dba75ec01f403497049d7cff111628edfe7b57278554dc798"; date: "Jan 12, 2014"; time: "12:23 <font size='2'>AM</font>"; amount: "0.<font size='2'>000709159241</font>"; balance: "19301.<font size='2'>870709159241</font>"; description: ""; out: false }
ListElement { paymentId: "faef56b9acf67a7dba75ec01f403497049d7cff111628edfe7b57278554dc798"; address: "faef56b9acf67a7dba75ec01f403497049d7cff111628edfe7b57278554dc798"; date: "Jan 12, 2014"; time: "12:23 <font size='2'>AM</font>"; amount: "0.<font size='2'>000709159241</font>"; balance: "19301.<font size='2'>870709159241</font>"; description: ""; out: false }
ListElement { paymentId: "faef56b9acf67a7dba75ec01f403497049d7cff111628edfe7b57278554dc798"; address: "faef56b9acf67a7dba75ec01f403497049d7cff111628edfe7b57278554dc798"; date: "Jan 12, 2014"; time: "12:23 <font size='2'>AM</font>"; amount: "0.<font size='2'>000709159241</font>"; balance: "19301.<font size='2'>870709159241</font>"; description: "Client from Australia"; out: false }
ListElement { paymentId: "faef56b9acf67a7dba75ec01f403497049d7cff111628edfe7b57278554dc798"; address: "faef56b9acf67a7dba75ec01f403497049d7cff111628edfe7b57278554dc798"; date: "Jan 12, 2014"; time: "12:23 <font size='2'>AM</font>"; amount: "0.<font size='2'>000709159241</font>"; balance: "19301.<font size='2'>870709159241</font>"; description: ""; out: false }
onSortRequest: {
console.log("column: " + column + " desc: " + desc)
switch (column) {
case 0:
// Payment ID
model.sortRole = TransactionHistoryModel.TransactionPaymentIdRole
break;
case 1:
// Date;
model.sortRole = TransactionHistoryModel.TransactionDateRole
break;
case 2:
// Amount;
model.sortRole = TransactionHistoryModel.TransactionAmountRole
break;
}
model.sort(0, desc ? Qt.DescendingOrder : Qt.AscendingOrder)
}
}
Scroll {
@ -363,7 +422,7 @@ Rectangle {
anchors.leftMargin: 14
anchors.rightMargin: 14
onContentYChanged: flickableScroll.flickableContentYChanged()
model: testModel
model: root.model
}
}
}

View file

@ -1,21 +1,21 @@
// Copyright (c) 2014-2015, 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
@ -32,7 +32,7 @@ import QtQuick.Controls.Styles 1.4
import QtQuick.Layouts 1.1
import "../components"
import moneroComponents 1.0
import moneroComponents.Clipboard 1.0
Rectangle {
@ -43,14 +43,14 @@ Rectangle {
function updatePaymentId() {
var payment_id = appWindow.persistentSettings.payment_id
if (payment_id.length === 0) {
payment_id = appWindow.wallet.generatePaymentId()
if (typeof appWindow.currentWallet !== 'undefined') {
payment_id = appWindow.currentWallet.generatePaymentId()
appWindow.persistentSettings.payment_id = payment_id
appWindow.currentWallet.payment_id = payment_id
addressLine.text = appWindow.currentWallet.address
integratedAddressLine.text = appWindow.currentWallet.integratedAddress(payment_id)
}
paymentIdLine.text = payment_id
addressLine.text = appWindow.currentWallet.address
integratedAddressLine.text = appWindow.currentWallet.integratedAddress(payment_id)
}
Clipboard { id: clipboard }
@ -87,6 +87,8 @@ Rectangle {
readOnly: true
width: mainLayout.editWidth
Layout.fillWidth: true
onTextChanged: cursorPosition = 0
IconButton {
imageSource: "../images/copyToClipboard.png"
onClicked: {
@ -116,6 +118,9 @@ Rectangle {
readOnly: true
width: mainLayout.editWidth
Layout.fillWidth: true
onTextChanged: cursorPosition = 0
IconButton {
imageSource: "../images/copyToClipboard.png"
onClicked: {
@ -176,9 +181,13 @@ Rectangle {
}
Component.onCompleted: {
function onPageCompleted() {
console.log("Receive page loaded");
updatePaymentId()
if(addressLine.text.length == 0) {
updatePaymentId()
}
}
}

View file

@ -27,8 +27,264 @@
// 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 "../components"
import moneroComponents.Clipboard 1.0
Rectangle {
width: 100
height: 62
property var daemonAddress
color: "#F0EEEE"
Clipboard { id: clipboard }
function initSettings() {
// Mnemonic seed settings
memoTextInput.text = qsTr("Click button to show seed") + translationManager.emptyString
showSeedButton.visible = true
// Daemon settings
daemonAddress = persistentSettings.daemon_address.split(":");
console.log("address: " + persistentSettings.daemon_address)
// try connecting to daemon
var connectedToDaemon = currentWallet.connectToDaemon();
if(!connectedToDaemon){
console.log("not connected");
//TODO: Print error?
//daemonStatusText.text = qsTr("Unable to connect to Daemon.")
//daemonStatusText.visible = true
}
}
PasswordDialog {
id: settingsPasswordDialog
standardButtons: StandardButton.Ok + StandardButton.Cancel
onAccepted: {
if(appWindow.password == settingsPasswordDialog.password){
memoTextInput.text = currentWallet.seed
showSeedButton.visible = false
}
}
onRejected: {
}
onDiscard: {
}
}
ColumnLayout {
id: mainLayout
anchors.margins: 40
anchors.left: parent.left
anchors.top: parent.top
anchors.right: parent.right
spacing: 10
Label {
id: seedLabel
color: "#4A4949"
fontSize: 16
text: qsTr("Mnemonic seed: ") + translationManager.emptyString
Layout.preferredWidth: 100
Layout.alignment: Qt.AlignLeft
}
TextArea {
id: memoTextInput
textMargin: 6
font.family: "Arial"
font.pointSize: 14
wrapMode: TextEdit.WordWrap
readOnly: true
selectByMouse: true
Layout.fillWidth: true
Layout.preferredHeight: 100
Layout.alignment: Qt.AlignHCenter
text: qsTr("Click button to show seed") + translationManager.emptyString
Image {
id : clipboardButton
anchors.right: memoTextInput.right
anchors.bottom: memoTextInput.bottom
source: "qrc:///images/greyTriangle.png"
Image {
anchors.centerIn: parent
source: "qrc:///images/copyToClipboard.png"
}
MouseArea {
anchors.fill: parent
cursorShape: Qt.PointingHandCursor
onClicked: clipboard.setText(memoTextInput.text)
}
}
}
RowLayout {
Layout.fillWidth: true
Text {
id: wordsTipText
font.family: "Arial"
font.pointSize: 12
color: "#4A4646"
Layout.fillWidth: true
wrapMode: Text.WordWrap
text: qsTr("It is very important to write it down as this is the only backup you will need for your wallet.")
+ translationManager.emptyString
}
StandardButton {
id: showSeedButton
fontSize: 14
shadowReleasedColor: "#FF4304"
shadowPressedColor: "#B32D00"
releasedColor: "#FF6C3C"
pressedColor: "#FF4304"
text: qsTr("Show seed")
Layout.alignment: Qt.AlignRight
Layout.preferredWidth: 100
onClicked: {
settingsPasswordDialog.open();
}
}
}
Rectangle {
Layout.fillWidth: true
height: 1
color: "#DEDEDE"
}
RowLayout {
id: daemonAddrRow
Layout.fillWidth: true
Layout.preferredHeight: 40
Layout.topMargin: 40
spacing: 10
Label {
id: daemonAddrLabel
Layout.fillWidth: true
color: "#4A4949"
text: qsTr("Daemon adress") + translationManager.emptyString
fontSize: 16
}
LineEdit {
id: daemonAddr
Layout.preferredWidth: 200
Layout.fillWidth: true
text: (daemonAddress !== undefined) ? daemonAddress[0] : ""
placeholderText: qsTr("Hostname / IP")
}
LineEdit {
id: daemonPort
Layout.preferredWidth: 100
Layout.fillWidth: true
text: (daemonAddress !== undefined) ? daemonAddress[1] : ""
placeholderText: qsTr("Port")
}
StandardButton {
id: daemonAddrSave
Layout.fillWidth: false
Layout.leftMargin: 30
Layout.minimumWidth: 100
width: 60
text: qsTr("Save") + translationManager.emptyString
shadowReleasedColor: "#FF4304"
shadowPressedColor: "#B32D00"
releasedColor: "#FF6C3C"
pressedColor: "#FF4304"
visible: true
onClicked: {
console.log("saving daemon adress settings")
var newDaemon = daemonAddr.text + ":" + daemonPort.text
if(persistentSettings.daemon_address != newDaemon) {
persistentSettings.daemon_address = newDaemon
//reconnect wallet
appWindow.initialize();
}
}
}
}
RowLayout {
id: daemonStatusRow
Layout.fillWidth: true
Text {
id: daemonStatusText
font.family: "Arial"
font.pixelSize: 18
wrapMode: Text.Wrap
textFormat: Text.RichText
horizontalAlignment: Text.AlignHCenter
color: "#FF0000"
visible: true //!currentWallet.connected
}
// StandardButton {
// id: checkConnectionButton
// anchors.left: daemonStatusText.right
// anchors.leftMargin: 30
// width: 90
// text: qsTr("Check again") + translationManager.emptyString
// shadowReleasedColor: "#FF4304"
// shadowPressedColor: "#B32D00"
// releasedColor: "#FF6C3C"
// pressedColor: "#FF4304"
// visible: true
// onClicked: {
// checkDaemonConnection();
// }
// }
}
}
function onPageCompleted() {
console.log("Settings page loaded");
initSettings();
}
}

View file

@ -27,7 +27,7 @@
// THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
import QtQuick 2.0
import Bitmonero.PendingTransaction 1.0
import moneroComponents.PendingTransaction 1.0
import "../components"

View file

@ -116,5 +116,6 @@
<file>lang/flags/italy.png</file>
<file>components/PasswordDialog.qml</file>
<file>components/ProcessingSplash.qml</file>
<file>components/DaemonProgress.qml</file>
</qresource>
</RCC>

View file

@ -2,49 +2,89 @@
#include "TransactionInfo.h"
#include <wallet/wallet2_api.h>
#include <QDebug>
int TransactionHistory::count() const
{
return m_pimpl->count();
}
TransactionInfo *TransactionHistory::transaction(int index)
{
// box up Bitmonero::TransactionInfo
Bitmonero::TransactionInfo * impl = m_pimpl->transaction(index);
TransactionInfo * result = new TransactionInfo(impl, this);
return result;
if (index < 0 || index >= m_tinfo.size()) {
qCritical("%s: no transaction info for index %d", __FUNCTION__, index);
qCritical("%s: there's %d transactions in backend", __FUNCTION__, m_pimpl->count());
return nullptr;
}
return m_tinfo.at(index);
}
TransactionInfo *TransactionHistory::transaction(const QString &id)
{
// box up Bitmonero::TransactionInfo
Bitmonero::TransactionInfo * impl = m_pimpl->transaction(id.toStdString());
TransactionInfo * result = new TransactionInfo(impl, this);
return result;
}
//// XXX: not sure if this method really needed;
//TransactionInfo *TransactionHistory::transaction(const QString &id)
//{
// return nullptr;
//}
QList<TransactionInfo *> TransactionHistory::getAll() const
{
// XXX this invalidates previously saved history that might be used by model
emit refreshStarted();
qDeleteAll(m_tinfo);
m_tinfo.clear();
QDateTime firstDateTime = QDateTime::currentDateTime();
QDateTime lastDateTime = QDateTime(QDate(1970, 1, 1));
TransactionHistory * parent = const_cast<TransactionHistory*>(this);
for (const auto i : m_pimpl->getAll()) {
TransactionInfo * ti = new TransactionInfo(i, parent);
m_tinfo.append(ti);
// looking for transactions timestamp scope
if (ti->timestamp() >= lastDateTime) {
lastDateTime = ti->timestamp();
}
if (ti->timestamp() <= firstDateTime) {
firstDateTime = ti->timestamp();
}
}
emit refreshFinished();
if (m_firstDateTime != firstDateTime) {
m_firstDateTime = firstDateTime;
emit firstDateTimeChanged();
}
if (m_lastDateTime != lastDateTime) {
m_lastDateTime = lastDateTime;
emit lastDateTimeChanged();
}
return m_tinfo;
}
void TransactionHistory::refresh()
{
// XXX this invalidates previously saved history that might be used by clients
// rebuilding transaction list in wallet_api;
m_pimpl->refresh();
emit invalidated();
// copying list here and keep track on every item to avoid memleaks
getAll();
}
quint64 TransactionHistory::count() const
{
return m_tinfo.count();
}
QDateTime TransactionHistory::firstDateTime() const
{
return m_firstDateTime;
}
QDateTime TransactionHistory::lastDateTime() const
{
return m_lastDateTime;
}
TransactionHistory::TransactionHistory(Bitmonero::TransactionHistory *pimpl, QObject *parent)
: QObject(parent), m_pimpl(pimpl)
{
m_firstDateTime = QDateTime(QDate(1970, 1, 1));
m_lastDateTime = QDateTime::currentDateTime();
}

View file

@ -3,6 +3,7 @@
#include <QObject>
#include <QList>
#include <QDateTime>
namespace Bitmonero {
class TransactionHistory;
@ -14,16 +15,23 @@ class TransactionHistory : public QObject
{
Q_OBJECT
Q_PROPERTY(int count READ count)
Q_PROPERTY(QDateTime firstDateTime READ firstDateTime NOTIFY firstDateTimeChanged)
Q_PROPERTY(QDateTime lastDateTime READ lastDateTime NOTIFY lastDateTimeChanged)
public:
int count() const;
Q_INVOKABLE TransactionInfo *transaction(int index);
Q_INVOKABLE TransactionInfo * transaction(const QString &id);
// Q_INVOKABLE TransactionInfo * transaction(const QString &id);
Q_INVOKABLE QList<TransactionInfo*> getAll() const;
Q_INVOKABLE void refresh();
quint64 count() const;
QDateTime firstDateTime() const;
QDateTime lastDateTime() const;
signals:
void invalidated();
void refreshStarted() const;
void refreshFinished() const;
void firstDateTimeChanged() const;
void lastDateTimeChanged() const;
public slots:
@ -36,6 +44,8 @@ private:
Bitmonero::TransactionHistory * m_pimpl;
mutable QList<TransactionInfo*> m_tinfo;
mutable QDateTime m_firstDateTime;
mutable QDateTime m_lastDateTime;
};

View file

@ -1,4 +1,6 @@
#include "TransactionInfo.h"
#include "WalletManager.h"
#include <QDateTime>
TransactionInfo::Direction TransactionInfo::direction() const
@ -16,15 +18,21 @@ bool TransactionInfo::isFailed() const
return m_pimpl->isFailed();
}
quint64 TransactionInfo::amount() const
double TransactionInfo::amount() const
{
return m_pimpl->amount();
// there's no unsigned uint64 for JS, so better use double
return WalletManager::instance()->displayAmount(m_pimpl->amount()).toDouble();
}
quint64 TransactionInfo::fee() const
QString TransactionInfo::displayAmount() const
{
return m_pimpl->fee();
return WalletManager::instance()->displayAmount(m_pimpl->amount());
}
QString TransactionInfo::fee() const
{
return WalletManager::instance()->displayAmount(m_pimpl->fee());
}
quint64 TransactionInfo::blockHeight() const
@ -37,13 +45,23 @@ QString TransactionInfo::hash() const
return QString::fromStdString(m_pimpl->hash());
}
QString TransactionInfo::timestamp()
QDateTime TransactionInfo::timestamp() const
{
QString result = QDateTime::fromTime_t(m_pimpl->timestamp()).toString(Qt::ISODate);
QDateTime result = QDateTime::fromTime_t(m_pimpl->timestamp());
return result;
}
QString TransactionInfo::paymentId()
QString TransactionInfo::date() const
{
return timestamp().date().toString(Qt::ISODate);
}
QString TransactionInfo::time() const
{
return timestamp().time().toString(Qt::ISODate);
}
QString TransactionInfo::paymentId() const
{
return QString::fromStdString(m_pimpl->paymentId());
}

View file

@ -1,8 +1,9 @@
#ifndef TRANSACTIONINFO_H
#define TRANSACTIONINFO_H
#include <QObject>
#include <wallet/wallet2_api.h>
#include <QObject>
#include <QDateTime>
class TransactionInfo : public QObject
{
@ -10,19 +11,25 @@ class TransactionInfo : public QObject
Q_PROPERTY(Direction direction READ direction)
Q_PROPERTY(bool isPending READ isPending)
Q_PROPERTY(bool isFailed READ isFailed)
Q_PROPERTY(quint64 amount READ amount)
Q_PROPERTY(quint64 fee READ fee)
Q_PROPERTY(double amount READ amount)
Q_PROPERTY(QString displayAmount READ displayAmount)
Q_PROPERTY(QString fee READ fee)
Q_PROPERTY(quint64 blockHeight READ blockHeight)
Q_PROPERTY(QString hash READ hash)
Q_PROPERTY(QString timestamp READ timestamp)
Q_PROPERTY(QDateTime timestamp READ timestamp)
Q_PROPERTY(QString date READ date)
Q_PROPERTY(QString time READ time)
Q_PROPERTY(QString paymentId READ paymentId)
public:
enum Direction {
Direction_In = Bitmonero::TransactionInfo::Direction_In,
Direction_Out = Bitmonero::TransactionInfo::Direction_Out
Direction_Out = Bitmonero::TransactionInfo::Direction_Out,
Direction_Both // invalid direction value, used for filtering
};
Q_ENUM(Direction)
// TODO: implement as separate class;
// struct Transfer {
@ -30,16 +37,21 @@ public:
// const uint64_t amount;
// const std::string address;
// };
Direction direction() const;
bool isPending() const;
bool isFailed() const;
quint64 amount() const;
quint64 fee() const;
double amount() const;
QString displayAmount() const;
QString fee() const;
quint64 blockHeight() const;
//! transaction_id
QString hash() const;
QString timestamp();
QString paymentId();
QDateTime timestamp() const;
QString date() const;
QString time() const;
QString paymentId() const;
// TODO: implement it
//! only applicable for output transactions
@ -51,4 +63,8 @@ private:
Bitmonero::TransactionInfo * m_pimpl;
};
// in order to wrap it to QVariant
Q_DECLARE_METATYPE(TransactionInfo*)
#endif // TRANSACTIONINFO_H

View file

@ -1,6 +1,8 @@
#include "Wallet.h"
#include "PendingTransaction.h"
#include "TransactionHistory.h"
#include "model/TransactionHistoryModel.h"
#include "model/TransactionHistorySortFilterModel.h"
#include "wallet/wallet2_api.h"
#include <QFile>
@ -31,7 +33,6 @@ public:
virtual void moneyReceived(const std::string &txId, uint64_t amount)
{
qDebug() << __FUNCTION__;
emit m_wallet->moneyReceived(QString::fromStdString(txId), amount);
}
@ -59,7 +60,10 @@ private:
Wallet * m_wallet;
};
Wallet::Wallet(QObject * parent)
: Wallet(nullptr, parent)
{
}
QString Wallet::getSeed() const
{
@ -86,6 +90,11 @@ bool Wallet::connected() const
return m_walletImpl->connected();
}
bool Wallet::synchronized() const
{
return m_walletImpl->synchronized();
}
QString Wallet::errorString() const
{
return QString::fromStdString(m_walletImpl->errorString());
@ -153,9 +162,16 @@ quint64 Wallet::daemonBlockChainHeight() const
return m_daemonBlockChainHeight;
}
quint64 Wallet::daemonBlockChainTargetHeight() const
{
m_daemonBlockChainTargetHeight = m_walletImpl->daemonBlockChainTargetHeight();
return m_daemonBlockChainTargetHeight;
}
bool Wallet::refresh()
{
bool result = m_walletImpl->refresh();
m_history->refresh();
if (result)
emit updated();
return result;
@ -193,15 +209,24 @@ void Wallet::disposeTransaction(PendingTransaction *t)
delete t;
}
TransactionHistory *Wallet::history()
TransactionHistory *Wallet::history() const
{
if (!m_history) {
Bitmonero::TransactionHistory * impl = m_walletImpl->history();
m_history = new TransactionHistory(impl, this);
}
return m_history;
}
TransactionHistorySortFilterModel *Wallet::historyModel() const
{
if (!m_historyModel) {
Wallet * w = const_cast<Wallet*>(this);
m_historyModel = new TransactionHistoryModel(w);
m_historyModel->setTransactionHistory(this->history());
m_historySortFilterModel = new TransactionHistorySortFilterModel(w);
m_historySortFilterModel->setSourceModel(m_historyModel);
}
return m_historySortFilterModel;
}
QString Wallet::generatePaymentId() const
{
@ -228,13 +253,16 @@ Wallet::Wallet(Bitmonero::Wallet *w, QObject *parent)
: QObject(parent)
, m_walletImpl(w)
, m_history(nullptr)
, m_historyModel(nullptr)
, m_daemonBlockChainHeight(0)
, m_daemonBlockChainHeightTtl(DAEMON_BLOCKCHAIN_HEIGHT_CACHE_TTL_SECONDS)
{
m_history = new TransactionHistory(m_walletImpl->history(), this);
m_walletImpl->setListener(new WalletListenerImpl(this));
}
Wallet::~Wallet()
{
Bitmonero::WalletManagerFactory::getWalletManager()->closeWallet(m_walletImpl);
}

View file

@ -13,6 +13,8 @@ namespace Bitmonero {
class TransactionHistory;
class TransactionHistoryModel;
class TransactionHistorySortFilterModel;
class Wallet : public QObject
{
@ -21,14 +23,18 @@ class Wallet : public QObject
Q_PROPERTY(QString seedLanguage READ getSeedLanguage)
Q_PROPERTY(Status status READ status)
Q_PROPERTY(bool connected READ connected)
Q_PROPERTY(bool synchronized READ synchronized)
Q_PROPERTY(QString errorString READ errorString)
Q_PROPERTY(QString address READ address)
Q_PROPERTY(quint64 balance READ balance)
Q_PROPERTY(quint64 unlockedBalance READ unlockedBalance)
Q_PROPERTY(TransactionHistory * history READ history)
Q_PROPERTY(QString paymentId READ paymentId WRITE setPaymentId)
Q_PROPERTY(TransactionHistorySortFilterModel * historyModel READ historyModel NOTIFY historyModelChanged)
public:
enum Status {
Status_Ok = Bitmonero::Wallet::Status_Ok,
Status_Error = Bitmonero::Wallet::Status_Error
@ -48,9 +54,13 @@ public:
//! returns last operation's status
Status status() const;
//! returns of wallet connected
//! returns true if wallet connected
bool connected() const;
//! returns true if wallet was ever synchronized
bool synchronized() const;
//! returns last operation's error message
QString errorString() const;
@ -88,10 +98,12 @@ public:
//! returns daemon's blockchain height
Q_INVOKABLE quint64 daemonBlockChainHeight() const;
//! returns daemon's blockchain target height
Q_INVOKABLE quint64 daemonBlockChainTargetHeight() const;
//! refreshes the wallet
Q_INVOKABLE bool refresh();
//! refreshes the wallet asynchronously
Q_INVOKABLE void refreshAsync();
@ -109,7 +121,10 @@ public:
Q_INVOKABLE void disposeTransaction(PendingTransaction * t);
//! returns transaction history
TransactionHistory * history();
TransactionHistory * history() const;
//! returns transaction history model
TransactionHistorySortFilterModel *historyModel() const;
//! generate payment id
Q_INVOKABLE QString generatePaymentId() const;
@ -136,12 +151,13 @@ signals:
void moneySpent(const QString &txId, quint64 amount);
void moneyReceived(const QString &txId, quint64 amount);
void newBlock(quint64 height);
void historyModelChanged() const;
private:
Wallet(QObject * parent = nullptr);
Wallet(Bitmonero::Wallet *w, QObject * parent = 0);
~Wallet();
private:
friend class WalletManager;
friend class WalletListenerImpl;
@ -149,11 +165,16 @@ private:
Bitmonero::Wallet * m_walletImpl;
// history lifetime managed by wallet;
TransactionHistory * m_history;
// Used for UI history view
mutable TransactionHistoryModel * m_historyModel;
mutable TransactionHistorySortFilterModel * m_historySortFilterModel;
QString m_paymentId;
mutable QTime m_daemonBlockChainHeightTime;
mutable quint64 m_daemonBlockChainHeight;
int m_daemonBlockChainHeightTtl;
mutable quint64 m_daemonBlockChainTargetHeight;
};
#endif // WALLET_H

View file

@ -61,9 +61,9 @@ void WalletManager::openWalletAsync(const QString &path, const QString &password
}
Wallet *WalletManager::recoveryWallet(const QString &path, const QString &memo, bool testnet)
Wallet *WalletManager::recoveryWallet(const QString &path, const QString &memo, bool testnet, quint64 restoreHeight)
{
Bitmonero::Wallet * w = m_pimpl->recoveryWallet(path.toStdString(), memo.toStdString(), testnet);
Bitmonero::Wallet * w = m_pimpl->recoveryWallet(path.toStdString(), memo.toStdString(), testnet, restoreHeight);
Wallet * wallet = new Wallet(w);
return wallet;
}

View file

@ -48,7 +48,7 @@ public:
// wizard: recoveryWallet path; hint: internally it recorvers wallet and set password = ""
Q_INVOKABLE Wallet * recoveryWallet(const QString &path, const QString &memo,
bool testnet = false);
bool testnet = false, quint64 restoreHeight = 0);
/*!
* \brief closeWallet - closes wallet and frees memory

View file

@ -0,0 +1,126 @@
#include "TransactionHistoryModel.h"
#include "TransactionHistory.h"
#include "TransactionInfo.h"
#include <QDateTime>
TransactionHistoryModel::TransactionHistoryModel(QObject *parent)
: QAbstractListModel(parent), m_transactionHistory(nullptr)
{
}
void TransactionHistoryModel::setTransactionHistory(TransactionHistory *th)
{
beginResetModel();
m_transactionHistory = th;
endResetModel();
connect(m_transactionHistory, &TransactionHistory::refreshStarted,
this, &TransactionHistoryModel::beginResetModel);
connect(m_transactionHistory, &TransactionHistory::refreshFinished,
this, &TransactionHistoryModel::endResetModel);
emit transactionHistoryChanged();
}
TransactionHistory *TransactionHistoryModel::transactionHistory() const
{
return m_transactionHistory;
}
QVariant TransactionHistoryModel::data(const QModelIndex &index, int role) const
{
if (!m_transactionHistory) {
return QVariant();
}
if (index.row() < 0 || (unsigned)index.row() >= m_transactionHistory->count()) {
return QVariant();
}
TransactionInfo * tInfo = m_transactionHistory->transaction(index.row());
Q_ASSERT(tInfo);
if (!tInfo) {
qCritical("%s: internal error: no transaction info for index %d", __FUNCTION__, index.row());
return QVariant();
}
QVariant result;
switch (role) {
case TransactionRole:
result = QVariant::fromValue(tInfo);
break;
case TransactionDirectionRole:
result = QVariant::fromValue(tInfo->direction());
break;
case TransactionPendingRole:
result = tInfo->isPending();
break;
case TransactionFailedRole:
result = tInfo->isFailed();
break;
case TransactionAmountRole:
result = tInfo->amount();
break;
case TransactionDisplayAmountRole:
result = tInfo->displayAmount();
break;
case TransactionFeeRole:
result = tInfo->fee();
break;
case TransactionBlockHeightRole:
result = tInfo->blockHeight();
break;
case TransactionHashRole:
result = tInfo->hash();
break;
case TransactionTimeStampRole:
result = tInfo->timestamp();
break;
case TransactionPaymentIdRole:
result = tInfo->paymentId();
break;
case TransactionIsOutRole:
result = tInfo->direction() == TransactionInfo::Direction_Out;
break;
case TransactionDateRole:
result = tInfo->date();
break;
case TransactionTimeRole:
result = tInfo->time();
break;
}
return result;
}
int TransactionHistoryModel::rowCount(const QModelIndex &parent) const
{
Q_UNUSED(parent)
return m_transactionHistory ? m_transactionHistory->count() : 0;
}
QHash<int, QByteArray> TransactionHistoryModel::roleNames() const
{
QHash<int, QByteArray> roleNames = QAbstractListModel::roleNames();
roleNames.insert(TransactionRole, "transaction");
roleNames.insert(TransactionDirectionRole, "direction");
roleNames.insert(TransactionPendingRole, "isPending");
roleNames.insert(TransactionFailedRole, "isFailed");
roleNames.insert(TransactionAmountRole, "amount");
roleNames.insert(TransactionDisplayAmountRole, "displayAmount");
roleNames.insert(TransactionFeeRole, "fee");
roleNames.insert(TransactionBlockHeightRole, "blockHeight");
roleNames.insert(TransactionHashRole, "hash");
roleNames.insert(TransactionTimeStampRole, "timeStamp");
roleNames.insert(TransactionPaymentIdRole, "paymentId");
roleNames.insert(TransactionIsOutRole, "isOut");
roleNames.insert(TransactionDateRole, "date");
roleNames.insert(TransactionTimeRole, "time");
return roleNames;
}

View file

@ -0,0 +1,68 @@
#ifndef TRANSACTIONHISTORYMODEL_H
#define TRANSACTIONHISTORYMODEL_H
#include <QAbstractListModel>
class TransactionHistory;
class TransactionInfo;
/**
* @brief The TransactionHistoryModel class - read-only list model for Transaction History
*/
class TransactionHistoryModel : public QAbstractListModel
{
Q_OBJECT
Q_PROPERTY(TransactionHistory * transactionHistory READ transactionHistory WRITE setTransactionHistory NOTIFY transactionHistoryChanged)
public:
enum TransactionInfoRole {
TransactionRole = Qt::UserRole + 1, // for the TransactionInfo object;
TransactionDirectionRole,
TransactionPendingRole,
TransactionFailedRole,
TransactionAmountRole,
TransactionDisplayAmountRole,
TransactionFeeRole,
TransactionBlockHeightRole,
TransactionHashRole,
TransactionTimeStampRole,
TransactionPaymentIdRole,
// extra role (alias) for TransactionDirectionRole (as UI currently wants just boolean "out")
TransactionIsOutRole,
// extra roles for date and time (as UI wants date and time separately)
TransactionDateRole,
TransactionTimeRole
};
Q_ENUM(TransactionInfoRole)
TransactionHistoryModel(QObject * parent = 0);
void setTransactionHistory(TransactionHistory * th);
TransactionHistory * transactionHistory() const;
/**
* @brief dateFrom - returns firstmost transaction datetime
* @return
*/
QDateTime firstDateTime() const;
/**
* @brief dateTo - returns lastmost transaction datetime
* @return
*/
QDateTime lastDateTime() const;
/// QAbstractListModel
virtual QVariant data(const QModelIndex & index, int role = Qt::DisplayRole) const override;
virtual int rowCount(const QModelIndex & parent = QModelIndex()) const override;
virtual QHash<int, QByteArray> roleNames() const override;
signals:
void transactionHistoryChanged();
private:
TransactionHistory * m_transactionHistory;
};
#endif // TRANSACTIONHISTORYMODEL_H

View file

@ -0,0 +1,209 @@
#include "TransactionHistorySortFilterModel.h"
#include "TransactionHistoryModel.h"
#include <QDebug>
namespace {
/**
* helper to extract scope value from filter
*/
template <typename T>
T scopeFilterValue(const QMap<int, QVariant> &filters, int role, int scopeIndex)
{
if (!filters.contains(role)) {
return T();
}
return filters.value(role).toList().at(scopeIndex).value<T>();
}
/**
* helper to setup scope value to filter
*/
template <typename T>
void setScopeFilterValue(QMap<int, QVariant> &filters, int role, int scopeIndex, const T &value)
{
QVariantList scopeFilter;
if (filters.contains(role)) {
scopeFilter = filters.value(role).toList();
}
while (scopeFilter.size() < 2) {
scopeFilter.append(T());
}
scopeFilter[scopeIndex] = QVariant::fromValue(value);
filters[role] = scopeFilter;
}
}
TransactionHistorySortFilterModel::TransactionHistorySortFilterModel(QObject *parent)
: QSortFilterProxyModel(parent)
{
setDynamicSortFilter(true);
}
QString TransactionHistorySortFilterModel::paymentIdFilter() const
{
return m_filterValues.value(TransactionHistoryModel::TransactionPaymentIdRole).toString();
}
void TransactionHistorySortFilterModel::setPaymentIdFilter(const QString &arg)
{
if (paymentIdFilter() != arg) {
m_filterValues[TransactionHistoryModel::TransactionPaymentIdRole] = arg;
emit paymentIdFilterChanged();
invalidateFilter();
}
}
QDate TransactionHistorySortFilterModel::dateFromFilter() const
{
return scopeFilterValue<QDate>(m_filterValues, TransactionHistoryModel::TransactionTimeStampRole, ScopeIndex::From);
}
void TransactionHistorySortFilterModel::setDateFromFilter(const QDate &date)
{
if (date != dateFromFilter()) {
setScopeFilterValue(m_filterValues, TransactionHistoryModel::TransactionTimeStampRole, ScopeIndex::From, date);
emit dateFromFilterChanged();
invalidateFilter();
}
}
QDate TransactionHistorySortFilterModel::dateToFilter() const
{
return scopeFilterValue<QDate>(m_filterValues, TransactionHistoryModel::TransactionTimeStampRole, ScopeIndex::To);
}
void TransactionHistorySortFilterModel::setDateToFilter(const QDate &date)
{
if (date != dateToFilter()) {
setScopeFilterValue(m_filterValues, TransactionHistoryModel::TransactionTimeStampRole, ScopeIndex::To, date);
emit dateToFilterChanged();
invalidateFilter();
}
}
double TransactionHistorySortFilterModel::amountFromFilter() const
{
return scopeFilterValue<double>(m_filterValues, TransactionHistoryModel::TransactionAmountRole, ScopeIndex::From);
}
void TransactionHistorySortFilterModel::setAmountFromFilter(double value)
{
if (value != amountFromFilter()) {
setScopeFilterValue(m_filterValues, TransactionHistoryModel::TransactionAmountRole, ScopeIndex::From, value);
emit amountFromFilterChanged();
invalidateFilter();
}
}
double TransactionHistorySortFilterModel::amountToFilter() const
{
return scopeFilterValue<double>(m_filterValues, TransactionHistoryModel::TransactionAmountRole, ScopeIndex::To);
}
void TransactionHistorySortFilterModel::setAmountToFilter(double value)
{
if (value != amountToFilter()) {
setScopeFilterValue(m_filterValues, TransactionHistoryModel::TransactionAmountRole, ScopeIndex::To, value);
emit amountToFilterChanged();
invalidateFilter();
}
}
int TransactionHistorySortFilterModel::directionFilter() const
{
return m_filterValues.value(TransactionHistoryModel::TransactionDirectionRole).value<TransactionInfo::Direction>();
}
void TransactionHistorySortFilterModel::setDirectionFilter(int value)
{
if (value != directionFilter()) {
m_filterValues[TransactionHistoryModel::TransactionDirectionRole] = QVariant::fromValue(value);
emit directionFilterChanged();
invalidateFilter();
}
}
void TransactionHistorySortFilterModel::sort(int column, Qt::SortOrder order)
{
QSortFilterProxyModel::sort(column, order);
}
TransactionHistory *TransactionHistorySortFilterModel::transactionHistory() const
{
const TransactionHistoryModel * model = static_cast<const TransactionHistoryModel*> (sourceModel());
return model->transactionHistory();
}
bool TransactionHistorySortFilterModel::filterAcceptsRow(int source_row, const QModelIndex &source_parent) const
{
if (source_row < 0 || source_row >= sourceModel()->rowCount()) {
return false;
}
QModelIndex index = sourceModel()->index(source_row, 0, source_parent);
if (!index.isValid()) {
return false;
}
bool result = true;
// iterating through filters
for (int role : m_filterValues.keys()) {
if (m_filterValues.contains(role)) {
QVariant data = sourceModel()->data(index, role);
switch (role) {
case TransactionHistoryModel::TransactionPaymentIdRole:
result = data.toString().contains(paymentIdFilter());
break;
case TransactionHistoryModel::TransactionTimeStampRole:
{
QDateTime from = QDateTime(dateFromFilter());
QDateTime to = QDateTime(dateToFilter());
to = to.addDays(1); // including upperbound
QDateTime timestamp = data.toDateTime();
bool matchFrom = from.isNull() || timestamp.isNull() || timestamp >= from;
bool matchTo = to.isNull() || timestamp.isNull() || timestamp <= to;
result = matchFrom && matchTo;
}
break;
case TransactionHistoryModel::TransactionAmountRole:
{
double from = amountFromFilter();
double to = amountToFilter();
double amount = data.toDouble();
bool matchFrom = from <= 0 || amount >= from;
bool matchTo = to <= 0 || amount <= to;
result = matchFrom && matchTo;
}
break;
case TransactionHistoryModel::TransactionDirectionRole:
result = directionFilter() == TransactionInfo::Direction_Both ? true
: data.toInt() == directionFilter();
break;
default:
break;
}
if (!result) { // stop the loop once filter doesn't match
break;
}
}
}
return result;
}
bool TransactionHistorySortFilterModel::lessThan(const QModelIndex &source_left, const QModelIndex &source_right) const
{
return QSortFilterProxyModel::lessThan(source_left, source_right);
}

View file

@ -0,0 +1,79 @@
#ifndef TRANSACTIONHISTORYSORTFILTERMODEL_H
#define TRANSACTIONHISTORYSORTFILTERMODEL_H
#include "TransactionInfo.h"
#include <QSortFilterProxyModel>
#include <QMap>
#include <QVariant>
#include <QDate>
class TransactionHistory;
class TransactionHistorySortFilterModel: public QSortFilterProxyModel
{
Q_OBJECT
Q_PROPERTY(QString paymentIdFilter READ paymentIdFilter WRITE setPaymentIdFilter NOTIFY paymentIdFilterChanged)
Q_PROPERTY(QDate dateFromFilter READ dateFromFilter WRITE setDateFromFilter NOTIFY dateFromFilterChanged)
Q_PROPERTY(QDate dateToFilter READ dateToFilter WRITE setDateToFilter NOTIFY dateToFilterChanged)
Q_PROPERTY(double amountFromFilter READ amountFromFilter WRITE setAmountFromFilter NOTIFY amountFromFilterChanged)
Q_PROPERTY(double amountToFilter READ amountToFilter WRITE setAmountToFilter NOTIFY amountToFilterChanged)
Q_PROPERTY(int directionFilter READ directionFilter WRITE setDirectionFilter NOTIFY directionFilterChanged)
Q_PROPERTY(TransactionHistory * transactionHistory READ transactionHistory)
public:
TransactionHistorySortFilterModel(QObject * parent = nullptr);
//! filtering by payment id
QString paymentIdFilter() const;
void setPaymentIdFilter(const QString &arg);
//! filtering by date (lower bound)
QDate dateFromFilter() const;
void setDateFromFilter(const QDate &date);
//! filtering by to date (upper bound)
QDate dateToFilter() const;
void setDateToFilter(const QDate &date);
//! filtering by amount (lower bound)
double amountFromFilter() const;
void setAmountFromFilter(double value);
//! filtering by amount (upper bound)
double amountToFilter() const;
void setAmountToFilter(double value);
//! filtering by direction
int directionFilter() const;
void setDirectionFilter(int value);
Q_INVOKABLE void sort(int column, Qt::SortOrder order);
TransactionHistory * transactionHistory() const;
signals:
void paymentIdFilterChanged();
void dateFromFilterChanged();
void dateToFilterChanged();
void amountFromFilterChanged();
void amountToFilterChanged();
void directionFilterChanged();
protected:
// QSortFilterProxyModel overrides
virtual bool filterAcceptsRow(int source_row, const QModelIndex &source_parent) const;
virtual bool lessThan(const QModelIndex &source_left, const QModelIndex &source_right) const;
private:
enum ScopeIndex {
From = 0,
To = 1
};
private:
QMap<int, QVariant> m_filterValues;
};
#endif // TRANSACTIONHISTORYSORTFILTERMODEL_H

View file

@ -27,7 +27,9 @@
// THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
import QtQuick 2.2
import moneroComponents 1.0
import moneroComponents.WalletManager 1.0
import moneroComponents.Wallet 1.0
import QtQuick.Dialogs 1.2
import 'utils.js' as Utils
@ -94,5 +96,6 @@ Item {
wordsTextItem.clipboardButtonVisible: true
wordsTextItem.tipTextVisible: true
wordsTextItem.memoTextReadOnly: true
restoreHeightVisible:false
}
}

View file

@ -47,6 +47,7 @@ Item {
+ qsTr("<b>Allow background mining: </b>") + wizard.settings['allow_background_mining'] + "<br>"
+ qsTr("<b>Daemon address: </b>") + wizard.settings['daemon_address'] + "<br>"
+ qsTr("<b>testnet: </b>") + wizard.settings['testnet'] + "<br>"
+ (wizard.settings['restore_height'] === undefined ? "" : qsTr("<b>Restore height: </b>") + wizard.settings['restore_height']) + "<br>"
+ translationManager.emptyString
return str;
}

View file

@ -137,6 +137,7 @@ Rectangle {
appWindow.persistentSettings.auto_donations_amount = settings.auto_donations_amount
appWindow.persistentSettings.daemon_address = settings.daemon_address
appWindow.persistentSettings.testnet = settings.testnet
appWindow.persistentSettings.restore_height = parseInt(settings.restore_height)
}

View file

@ -27,8 +27,10 @@
// THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
import QtQuick 2.2
import moneroComponents 1.0
import moneroComponents.TranslationManager 1.0
import QtQuick.Dialogs 1.2
import QtQuick.Layouts 1.1
import "../components"
// Reusable component for managing wallet (account name, path, private key)
@ -39,6 +41,8 @@ Item {
property alias wordsTextTitle: frameHeader.text
property alias walletPath: fileUrlInput.text
property alias wordsTextItem : memoTextItem
property alias restoreHeight : restoreHeightItem.text
property alias restoreHeightVisible: restoreHeightItem.visible
// TODO extend properties if needed
@ -112,7 +116,7 @@ Item {
width: 300
height: 62
TextInput {
TextEdit {
id: accountName
anchors.fill: parent
horizontalAlignment: TextInput.AlignHCenter
@ -159,10 +163,22 @@ Item {
anchors.topMargin: 16
}
// Restore Height
LineEdit {
id: restoreHeightItem
anchors.top: memoTextItem.bottom
width: 250
anchors.topMargin: 20
placeholderText: qsTr("Restore height")
Layout.alignment: Qt.AlignCenter
validator: IntValidator {
bottom:0
}
}
Row {
anchors.left: parent.left
anchors.right: parent.right
anchors.top: memoTextItem.bottom
anchors.top: (restoreHeightItem.visible)? restoreHeightItem.bottom : memoTextItem.bottom
anchors.topMargin: 24
spacing: 16

View file

@ -1,5 +1,5 @@
import QtQuick 2.0
import moneroComponents 1.0
import moneroComponents.Clipboard 1.0
Column {

View file

@ -52,6 +52,8 @@ Item {
} else {
passwordPage.titleText = qsTr("Now that your wallet has been restored, please set a password for the wallet") + translationManager.emptyString
}
passwordItem.focus = true;
}
function onPageClosed(settingsObject) {
@ -146,30 +148,34 @@ Item {
anchors.topMargin: 24
width: 300
height: 62
placeholderText : qsTr("Password") + translationManager.emptyString;
KeyNavigation.tab: retypePasswordItem
onChanged: handlePassword()
}
WizardPasswordInput {
id: retypePasswordItem
anchors.top: passwordItem.bottom
anchors.horizontalCenter: parent.horizontalCenter
anchors.topMargin: 24
width: 300
height: 62
placeholderText : qsTr("Confirm password") + translationManager.emptyString;
KeyNavigation.tab: passwordItem
onChanged: handlePassword()
}
PrivacyLevelSmall {
id: privacyLevel
anchors.left: parent.left
anchors.right: parent.right
anchors.top: passwordItem.bottom
anchors.topMargin: 24
anchors.top: retypePasswordItem.bottom
anchors.topMargin: 60
background: "#F0EEEE"
interactive: false
}
WizardPasswordInput {
id: retypePasswordItem
anchors.top: privacyLevel.bottom
anchors.horizontalCenter: parent.horizontalCenter
anchors.topMargin: 24
width: 300
height: 62
onChanged: handlePassword()
}
Component.onCompleted: {
console.log
}

View file

@ -27,24 +27,34 @@
// 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
Item {
FocusScope {
property alias password: password.text
property alias placeholderText: password.placeholderText
signal changed(string password)
TextInput {
TextField {
id : password
focus:true
anchors.fill: parent
horizontalAlignment: TextInput.AlignHCenter
horizontalAlignment: TextInput.AlignLeft
verticalAlignment: TextInput.AlignVCenter
font.family: "Arial"
font.pixelSize: 32
renderType: Text.NativeRendering
color: "#35B05A"
passwordCharacter: "•"
echoMode: TextInput.Password
focus: true
style: TextFieldStyle {
renderType: Text.NativeRendering
textColor: "#35B05A"
passwordCharacter: "•"
background: Rectangle {
radius: 0
border.width: 0
}
}
Keys.onReleased: {
changed(text)
}

View file

@ -27,9 +27,8 @@
// THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
import QtQuick 2.2
import moneroComponents 1.0
import QtQuick.Dialogs 1.2
import Bitmonero.Wallet 1.0
import moneroComponents.Wallet 1.0
import 'utils.js' as Utils
Item {
@ -55,12 +54,14 @@ Item {
settingsObject['account_name'] = uiItem.accountNameText
settingsObject['words'] = Utils.lineBreaksToSpaces(uiItem.wordsTextItem.memoText)
settingsObject['wallet_path'] = uiItem.walletPath
settingsObject['restoreHeight'] = parseInt(uiItem.restoreHeight)
return recoveryWallet(settingsObject)
}
function recoveryWallet(settingsObject) {
var testnet = appWindow.persistentSettings.testnet;
var wallet = walletManager.recoveryWallet(oshelper.temporaryFilename(), settingsObject.words, testnet);
var restoreHeight = settingsObject.restoreHeight;
var wallet = walletManager.recoveryWallet(oshelper.temporaryFilename(), settingsObject.words, testnet, restoreHeight);
var success = wallet.status === Wallet.Status_Ok;
if (success) {
settingsObject['wallet'] = wallet;
@ -81,6 +82,7 @@ Item {
wordsTextItem.tipTextVisible: false
wordsTextItem.memoTextReadOnly: false
wordsTextItem.memoText: ""
restoreHeightVisible: true
wordsTextItem.onMemoTextChanged: {
checkNextButton();
}

View file

@ -163,6 +163,7 @@ Item {
if (data !== null || data !== undefined) {
var locale = data.locale
translationManager.setLanguage(locale.split("_")[0]);
wizard.switchPage(true)
}
}
}