Merge pull request #41

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 "components"
import "pages" import "pages"
// mbg033 @ 2016-10-08: Not used anymore, to be deleted
Rectangle { Rectangle {
id: root id: root
width: 470 width: 470

View file

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

View file

@ -27,44 +27,103 @@
// THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. // THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
import QtQuick 2.2 import QtQuick 2.2
import QtQml 2.0
import QtQuick.Controls 2.0
import QtQuick.Layouts 1.1
import QtGraphicalEffects 1.0 import QtGraphicalEffects 1.0
import "pages"
Rectangle { Rectangle {
id: root 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 paymentClicked(string address, string paymentId, double amount, int mixinCount, int priority)
signal generatePaymentIdInvoked() signal generatePaymentIdInvoked()
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: [ states: [
State { State {
name: "Dashboard" name: "Dashboard"
PropertyChanges { target: loader; source: "pages/Dashboard.qml" } PropertyChanges { }
}, State { }, State {
name: "History" name: "History"
PropertyChanges { target: loader; source: "pages/History.qml" } PropertyChanges { target: root; currentView: historyView }
PropertyChanges { target: historyView; model: appWindow.currentWallet.historyModel }
}, State { }, State {
name: "Transfer" name: "Transfer"
PropertyChanges { target: loader; source: "pages/Transfer.qml" } PropertyChanges { target: root; currentView: transferView }
}, State { }, State {
name: "Receive" name: "Receive"
PropertyChanges { target: loader; source: "pages/Receive.qml" } PropertyChanges { target: root; currentView: receiveView }
}, State { }, State {
name: "AddressBook" name: "AddressBook"
PropertyChanges { target: loader; source: "pages/AddressBook.qml" } PropertyChanges { /*TODO*/ }
}, State { }, State {
name: "Settings" name: "Settings"
PropertyChanges { target: loader; source: "pages/Settings.qml" } PropertyChanges { target: root; currentView: settingsView }
}, State { }, State {
name: "Mining" name: "Mining"
PropertyChanges { target: loader; source: "pages/Mining.qml" } PropertyChanges { /*TODO*/ }
} }
] ]
// color stripe at the top
Row { Row {
id: styledRow id: styledRow
height: 4
anchors.top: parent.top
anchors.left: parent.left anchors.left: parent.left
anchors.right: parent.right 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: "#FFE00A" }
Rectangle { height: 4; width: parent.width / 5; color: "#6B0072" } Rectangle { height: 4; width: parent.width / 5; color: "#6B0072" }
@ -73,28 +132,127 @@ Rectangle {
Rectangle { height: 4; width: parent.width / 5; color: "#FF4F41" } Rectangle { height: 4; width: parent.width / 5; color: "#FF4F41" }
} }
Loader { ColumnLayout {
id: loader 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.left: parent.left
anchors.right: parent.right anchors.right: parent.right
anchors.top: styledRow.bottom
anchors.bottom: parent.bottom anchors.bottom: parent.bottom
onLoaded: { height: 1
console.log("Loaded " + item); 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 { Rectangle {
anchors.top: styledRow.bottom anchors.top: styledRow.bottom
anchors.bottom: parent.bottom anchors.bottom: parent.bottom
@ -117,12 +275,25 @@ Rectangle {
anchors.bottom: parent.bottom anchors.bottom: parent.bottom
height: 1 height: 1
color: "#DBDBDB" color: "#DBDBDB"
} }
// indicate disabled state
// indicates disabled state
Desaturate { Desaturate {
anchors.fill: parent anchors.fill: parent
source: parent source: parent
desaturation: root.enabled ? 0.0 : 1.0 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. // THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
import QtQuick 2.0 import QtQuick 2.0
import moneroComponents 1.0 import moneroComponents.Clipboard 1.0
ListView { ListView {
id: 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. // THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
import QtQuick 2.0 import QtQuick 2.0
import moneroComponents 1.0 import moneroComponents.Clipboard 1.0
ListView { ListView {
id: listView id: listView

View file

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

View file

@ -27,12 +27,14 @@
// THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. // THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
import QtQuick 2.0 import QtQuick 2.0
import moneroComponents 1.0 import moneroComponents.Clipboard 1.0
ListView { ListView {
id: listView id: listView
clip: true clip: true
boundsBehavior: ListView.StopAtBounds boundsBehavior: ListView.StopAtBounds
property var previousItem
footer: Rectangle { footer: Rectangle {
height: 127 height: 127
@ -48,7 +50,7 @@ ListView {
} }
} }
property var previousItem
delegate: Rectangle { delegate: Rectangle {
id: delegate id: delegate
height: 114 height: 114
@ -63,13 +65,13 @@ ListView {
anchors.right: parent.right anchors.right: parent.right
anchors.top: parent.top anchors.top: parent.top
anchors.topMargin: 14 anchors.topMargin: 14
// -- direction indicator
Rectangle { Rectangle {
id: dot id: dot
width: 14 width: 14
height: width height: width
radius: width / 2 radius: width / 2
color: out ? "#FF4F41" : "#36B05B" color: isOut ? "#FF4F41" : "#36B05B"
} }
Item { //separator Item { //separator
@ -77,6 +79,8 @@ ListView {
height: 14 height: 14
} }
// -- description aka recepient name from address book (TODO)
/*
Text { Text {
id: descriptionText id: descriptionText
width: text.length ? (descriptionArea.containsMouse ? parent.width - x - 12 : 120) : 0 width: text.length ? (descriptionArea.containsMouse ? parent.width - x - 12 : 120) : 0
@ -94,13 +98,15 @@ ListView {
hoverEnabled: true hoverEnabled: true
} }
} }
*/
/*
Item { //separator Item { //separator
width: descriptionText.width ? 12 : 0 width: descriptionText.width ? 12 : 0
height: 14 height: 14
visible: !descriptionArea.containsMouse visible: !descriptionArea.containsMouse
} }
*/
// -- address (in case outgoing transaction) - N/A in case of incoming
Text { Text {
id: addressText id: addressText
anchors.verticalCenter: dot.verticalCenter anchors.verticalCenter: dot.verticalCenter
@ -109,11 +115,11 @@ ListView {
font.family: "Arial" font.family: "Arial"
font.pixelSize: 14 font.pixelSize: 14
color: "#545454" color: "#545454"
text: address text: hash
visible: !descriptionArea.containsMouse // visible: !descriptionArea.containsMouse
} }
} }
// -- "PaymentID" title
Text { Text {
id: paymentLabel id: paymentLabel
anchors.left: parent.left anchors.left: parent.left
@ -128,7 +134,7 @@ ListView {
color: "#535353" color: "#535353"
text: paymentId !== "" ? qsTr("Payment ID:") + translationManager.emptyString : "" text: paymentId !== "" ? qsTr("Payment ID:") + translationManager.emptyString : ""
} }
// -- "PaymentID" value
Text { Text {
anchors.bottom: paymentLabel.bottom anchors.bottom: paymentLabel.bottom
anchors.left: paymentLabel.right anchors.left: paymentLabel.right
@ -143,7 +149,7 @@ ListView {
color: "#545454" color: "#545454"
text: paymentId text: paymentId
} }
// -- "Date", "Balance" and "Amound" section
Row { Row {
anchors.left: parent.left anchors.left: parent.left
anchors.bottom: parent.bottom anchors.bottom: parent.bottom
@ -155,6 +161,7 @@ ListView {
height: 14 height: 14
} }
// -- "Date" column
Column { Column {
anchors.top: parent.top anchors.top: parent.top
width: 215 width: 215
@ -189,10 +196,13 @@ ListView {
} }
} }
} }
// -- "Balance" column
// XXX: we don't have a balance
/*
Column { Column {
anchors.top: parent.top anchors.top: parent.top
width: 148 width: 148
visible: false
Text { Text {
anchors.left: parent.left anchors.left: parent.left
@ -210,7 +220,9 @@ ListView {
text: balance text: balance
} }
} }
*/
// -- "Amount column
Column { Column {
anchors.top: parent.top anchors.top: parent.top
width: 148 width: 148
@ -230,8 +242,8 @@ ListView {
anchors.bottomMargin: 3 anchors.bottomMargin: 3
font.family: "Arial" font.family: "Arial"
font.pixelSize: 16 font.pixelSize: 16
color: out ? "#FF4F41" : "#36B05B" color: isOut ? "#FF4F41" : "#36B05B"
text: out ? "↓" : "↑" text: isOut ? "↓" : "↑"
} }
Text { Text {
@ -239,22 +251,44 @@ ListView {
font.family: "Arial" font.family: "Arial"
font.pixelSize: 18 font.pixelSize: 18
font.letterSpacing: -1 font.letterSpacing: -1
color: out ? "#FF4F41" : "#36B05B" color: isOut ? "#FF4F41" : "#36B05B"
text: amount 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 { TableDropdown {
id: dropdown id: dropdown
anchors.right: parent.right anchors.right: parent.right
@ -282,5 +316,16 @@ ListView {
height: 1 height: 1
color: "#DBDBDB" 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 text: input.text
property alias validator: input.validator property alias validator: input.validator
property alias readOnly : input.readOnly property alias readOnly : input.readOnly
property alias cursorPosition: input.cursorPosition
property int fontSize: 18 property int fontSize: 18
@ -56,7 +57,7 @@ Item {
id: input id: input
anchors.fill: parent anchors.fill: parent
anchors.leftMargin: 4 anchors.leftMargin: 4
anchors.rightMargin: 4 anchors.rightMargin: 30
font.pixelSize: parent.fontSize font.pixelSize: parent.fontSize
} }
} }

View file

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

View file

@ -43,8 +43,13 @@ Window {
opacity: 0.7 opacity: 0.7
ColumnLayout { ColumnLayout {
id: rootLayout
anchors.horizontalCenter: parent.horizontalCenter anchors.horizontalCenter: parent.horizontalCenter
anchors.verticalCenter: parent.verticalCenter anchors.verticalCenter: parent.verticalCenter
anchors.left: parent.left
anchors.right: parent.right
anchors.leftMargin: 30
anchors.rightMargin: 30
BusyIndicator { BusyIndicator {
running: parent.visible running: parent.visible
@ -59,6 +64,7 @@ Window {
} }
horizontalAlignment: Text.AlignHCenter horizontalAlignment: Text.AlignHCenter
Layout.alignment: Qt.AlignVCenter | Qt.AlignHCenter Layout.alignment: Qt.AlignVCenter | Qt.AlignHCenter
Layout.fillWidth: true
} }
@ -69,6 +75,7 @@ Window {
} }
horizontalAlignment: Text.AlignHCenter horizontalAlignment: Text.AlignHCenter
Layout.alignment: Qt.AlignVCenter | Qt.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_BRANCH=master
# MONERO_URL=https://github.com/mbg033/monero.git # MONERO_URL=https://github.com/mbg033/monero.git
# MONERO_BRANCH=develop # 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 # thanks to SO: http://stackoverflow.com/a/20283965/4118915
CPU_CORE_COUNT=$(grep -c ^processor /proc/cpuinfo 2>/dev/null || sysctl -n hw.ncpu) CPU_CORE_COUNT=$(grep -c ^processor /proc/cpuinfo 2>/dev/null || sysctl -n hw.ncpu)
pushd $(pwd) pushd $(pwd)
@ -39,19 +44,19 @@ platform=$(get_platform)
if [ "$platform" == "darwin" ]; then if [ "$platform" == "darwin" ]; then
# Do something under Mac OS X platform # Do something under Mac OS X platform
echo "Configuring build for MacOS.." 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 elif [ "$platform" == "linux" ]; then
# Do something under GNU/Linux platform # Do something under GNU/Linux platform
echo "Configuring build for Linux.." 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 elif [ "$platform" == "mingw64" ]; then
# Do something under Windows NT platform # Do something under Windows NT platform
echo "Configuring build for MINGW64.." 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 elif [ "$platform" == "mingw32" ]; then
# Do something under Windows NT platform # Do something under Windows NT platform
echo "Configuring build for MINGW32.." 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 else
echo "Unsupported platform: $platform" echo "Unsupported platform: $platform"
popd popd

View file

@ -39,8 +39,10 @@
#include "Wallet.h" #include "Wallet.h"
#include "PendingTransaction.h" #include "PendingTransaction.h"
#include "TranslationManager.h" #include "TranslationManager.h"
#include "TransactionInfo.h"
#include "TransactionHistory.h"
#include "model/TransactionHistoryModel.h"
#include "model/TransactionHistorySortFilterModel.h"
int main(int argc, char *argv[]) int main(int argc, char *argv[])
@ -56,23 +58,38 @@ int main(int argc, char *argv[])
filter *eventFilter = new filter; filter *eventFilter = new filter;
app.installEventFilter(eventFilter); 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"); "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"); "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"); "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<PendingTransaction::Priority>();
qRegisterMetaType<TransactionInfo::Direction>();
qRegisterMetaType<TransactionHistoryModel::TransactionInfoRole>();
QQmlApplicationEngine engine; QQmlApplicationEngine engine;

114
main.qml
View file

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

View file

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

View file

@ -27,10 +27,28 @@
// THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. // THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
import QtQuick 2.0 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" import "../components"
Rectangle { Rectangle {
id: root
property var model
color: "#F0EEEE" 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 { Text {
id: filterHeaderText id: filterHeaderText
@ -47,6 +65,8 @@ Rectangle {
text: qsTr("Filter transactions history") + translationManager.emptyString text: qsTr("Filter transactions history") + translationManager.emptyString
} }
// Filter by Address input (senseless, removing)
/*
Label { Label {
id: addressLabel id: addressLabel
anchors.left: parent.left anchors.left: parent.left
@ -67,11 +87,14 @@ Rectangle {
anchors.rightMargin: 17 anchors.rightMargin: 17
anchors.topMargin: 5 anchors.topMargin: 5
} }
*/
// Filter by Payment ID input
Label { Label {
id: paymentIdLabel id: paymentIdLabel
anchors.left: parent.left anchors.left: parent.left
anchors.top: addressLine.bottom anchors.top: filterHeaderText.bottom // addressLine.bottom
anchors.leftMargin: 17 anchors.leftMargin: 17
anchors.topMargin: 17 anchors.topMargin: 17
text: qsTr("Payment ID <font size='2'>(Optional)</font>") + translationManager.emptyString text: qsTr("Payment ID <font size='2'>(Optional)</font>") + translationManager.emptyString
@ -84,12 +107,16 @@ Rectangle {
id: paymentIdLine id: paymentIdLine
anchors.left: parent.left anchors.left: parent.left
anchors.right: parent.right anchors.right: parent.right
anchors.top: paymentIdLabel.bottom anchors.top: paymentIdLabel.bottom // addressLabel.bottom
anchors.leftMargin: 17 anchors.leftMargin: 17
anchors.rightMargin: 17 anchors.rightMargin: 17
anchors.topMargin: 5 anchors.topMargin: 5
} }
// Filter by description input (not implemented yet)
/*
Label { Label {
id: descriptionLabel id: descriptionLabel
anchors.left: parent.left anchors.left: parent.left
@ -110,11 +137,14 @@ Rectangle {
anchors.rightMargin: 17 anchors.rightMargin: 17
anchors.topMargin: 5 anchors.topMargin: 5
} }
*/
// DateFrom picker
Label { Label {
id: dateFromText id: dateFromText
anchors.left: parent.left anchors.left: parent.left
anchors.top: descriptionLine.bottom anchors.top: paymentIdLine.bottom // descriptionLine.bottom
anchors.leftMargin: 17 anchors.leftMargin: 17
anchors.topMargin: 17 anchors.topMargin: 17
width: 156 width: 156
@ -132,10 +162,11 @@ Rectangle {
z: 2 z: 2
} }
// DateTo picker
Label { Label {
id: dateToText id: dateToText
anchors.left: dateFromText.right anchors.left: dateFromText.right
anchors.top: descriptionLine.bottom anchors.top: paymentIdLine.bottom //descriptionLine.bottom
anchors.leftMargin: 17 anchors.leftMargin: 17
anchors.topMargin: 17 anchors.topMargin: 17
text: qsTr("To") text: qsTr("To")
@ -163,10 +194,30 @@ Rectangle {
shadowPressedColor: "#2D002F" shadowPressedColor: "#2D002F"
releasedColor: "#6B0072" releasedColor: "#6B0072"
pressedColor: "#4D0051" 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 { CheckBox {
id: checkBox id: advancedFilteringCheckBox
text: qsTr("Advance filtering") text: qsTr("Advance filtering")
anchors.left: filterButton.right anchors.left: filterButton.right
anchors.bottom: filterButton.bottom anchors.bottom: filterButton.bottom
@ -193,9 +244,10 @@ Rectangle {
ListModel { ListModel {
id: transactionsModel id: transactionsModel
ListElement { column1: "SENT"; column2: "" } ListElement { column1: "ALL"; column2: ""; value: TransactionInfo.Direction_Both }
ListElement { column1: "RECIVE"; column2: "" } ListElement { column1: "SENT"; column2: ""; value: TransactionInfo.Direction_Out }
ListElement { column1: "ON HOLD"; column2: "" } ListElement { column1: "RECEIVED"; column2: ""; value: TransactionInfo.Direction_In }
} }
StandardDropdown { StandardDropdown {
@ -274,8 +326,11 @@ Rectangle {
anchors.fill: parent anchors.fill: parent
onClicked: { onClicked: {
parent.expanded = !parent.expanded parent.expanded = !parent.expanded
if(checkBox.checked) tableRect.height = Qt.binding(function(){ return parent.expanded ? tableRect.expandedHeight : tableRect.collapsedHeight }) if (advancedFilteringCheckBox.checked) {
else tableRect.height = Qt.binding(function(){ return parent.expanded ? tableRect.expandedHeight : tableRect.middleHeight }) 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 { ListModel {
id: columnsModel id: columnsModel
ListElement { columnName: "Address"; columnWidth: 127 }
ListElement { columnName: "Payment ID"; columnWidth: 127 }
ListElement { columnName: "Date"; columnWidth: 100 } ListElement { columnName: "Date"; columnWidth: 100 }
ListElement { columnName: "Amount"; columnWidth: 148 } ListElement { columnName: "Amount"; columnWidth: 148 }
ListElement { columnName: "Description"; columnWidth: 148 } // ListElement { columnName: "Description"; columnWidth: 148 }
} }
TableHeader { TableHeader {
@ -328,21 +384,24 @@ Rectangle {
anchors.rightMargin: 14 anchors.rightMargin: 14
dataModel: columnsModel dataModel: columnsModel
offset: 20 offset: 20
onSortRequest: console.log("column: " + column + " desc: " + desc) 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)
} }
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 }
} }
Scroll { Scroll {
@ -363,7 +422,7 @@ Rectangle {
anchors.leftMargin: 14 anchors.leftMargin: 14
anchors.rightMargin: 14 anchors.rightMargin: 14
onContentYChanged: flickableScroll.flickableContentYChanged() onContentYChanged: flickableScroll.flickableContentYChanged()
model: testModel model: root.model
} }
} }
} }

View file

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

View file

@ -27,8 +27,264 @@
// THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. // THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
import QtQuick 2.0 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 { 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. // THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
import QtQuick 2.0 import QtQuick 2.0
import Bitmonero.PendingTransaction 1.0 import moneroComponents.PendingTransaction 1.0
import "../components" import "../components"

View file

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

View file

@ -2,49 +2,89 @@
#include "TransactionInfo.h" #include "TransactionInfo.h"
#include <wallet/wallet2_api.h> #include <wallet/wallet2_api.h>
#include <QDebug>
int TransactionHistory::count() const
{
return m_pimpl->count();
}
TransactionInfo *TransactionHistory::transaction(int index) TransactionInfo *TransactionHistory::transaction(int index)
{ {
// box up Bitmonero::TransactionInfo
Bitmonero::TransactionInfo * impl = m_pimpl->transaction(index); if (index < 0 || index >= m_tinfo.size()) {
TransactionInfo * result = new TransactionInfo(impl, this); qCritical("%s: no transaction info for index %d", __FUNCTION__, index);
return result; 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) //// XXX: not sure if this method really needed;
{ //TransactionInfo *TransactionHistory::transaction(const QString &id)
// box up Bitmonero::TransactionInfo //{
Bitmonero::TransactionInfo * impl = m_pimpl->transaction(id.toStdString()); // return nullptr;
TransactionInfo * result = new TransactionInfo(impl, this); //}
return result;
}
QList<TransactionInfo *> TransactionHistory::getAll() const QList<TransactionInfo *> TransactionHistory::getAll() const
{ {
// XXX this invalidates previously saved history that might be used by model
emit refreshStarted();
qDeleteAll(m_tinfo); qDeleteAll(m_tinfo);
m_tinfo.clear(); m_tinfo.clear();
QDateTime firstDateTime = QDateTime::currentDateTime();
QDateTime lastDateTime = QDateTime(QDate(1970, 1, 1));
TransactionHistory * parent = const_cast<TransactionHistory*>(this); TransactionHistory * parent = const_cast<TransactionHistory*>(this);
for (const auto i : m_pimpl->getAll()) { for (const auto i : m_pimpl->getAll()) {
TransactionInfo * ti = new TransactionInfo(i, parent); TransactionInfo * ti = new TransactionInfo(i, parent);
m_tinfo.append(ti); 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; return m_tinfo;
} }
void TransactionHistory::refresh() void TransactionHistory::refresh()
{ {
// XXX this invalidates previously saved history that might be used by clients // rebuilding transaction list in wallet_api;
m_pimpl->refresh(); 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) TransactionHistory::TransactionHistory(Bitmonero::TransactionHistory *pimpl, QObject *parent)
: QObject(parent), m_pimpl(pimpl) : 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 <QObject>
#include <QList> #include <QList>
#include <QDateTime>
namespace Bitmonero { namespace Bitmonero {
class TransactionHistory; class TransactionHistory;
@ -14,16 +15,23 @@ class TransactionHistory : public QObject
{ {
Q_OBJECT Q_OBJECT
Q_PROPERTY(int count READ count) Q_PROPERTY(int count READ count)
Q_PROPERTY(QDateTime firstDateTime READ firstDateTime NOTIFY firstDateTimeChanged)
Q_PROPERTY(QDateTime lastDateTime READ lastDateTime NOTIFY lastDateTimeChanged)
public: public:
int count() const;
Q_INVOKABLE TransactionInfo *transaction(int index); 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 QList<TransactionInfo*> getAll() const;
Q_INVOKABLE void refresh(); Q_INVOKABLE void refresh();
quint64 count() const;
QDateTime firstDateTime() const;
QDateTime lastDateTime() const;
signals: signals:
void invalidated(); void refreshStarted() const;
void refreshFinished() const;
void firstDateTimeChanged() const;
void lastDateTimeChanged() const;
public slots: public slots:
@ -36,6 +44,8 @@ private:
Bitmonero::TransactionHistory * m_pimpl; Bitmonero::TransactionHistory * m_pimpl;
mutable QList<TransactionInfo*> m_tinfo; mutable QList<TransactionInfo*> m_tinfo;
mutable QDateTime m_firstDateTime;
mutable QDateTime m_lastDateTime;
}; };

View file

@ -1,4 +1,6 @@
#include "TransactionInfo.h" #include "TransactionInfo.h"
#include "WalletManager.h"
#include <QDateTime> #include <QDateTime>
TransactionInfo::Direction TransactionInfo::direction() const TransactionInfo::Direction TransactionInfo::direction() const
@ -16,15 +18,21 @@ bool TransactionInfo::isFailed() const
return m_pimpl->isFailed(); 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 quint64 TransactionInfo::blockHeight() const
@ -37,13 +45,23 @@ QString TransactionInfo::hash() const
return QString::fromStdString(m_pimpl->hash()); 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; 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()); return QString::fromStdString(m_pimpl->paymentId());
} }

View file

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

View file

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

View file

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

View file

@ -48,7 +48,7 @@ public:
// wizard: recoveryWallet path; hint: internally it recorvers wallet and set password = "" // wizard: recoveryWallet path; hint: internally it recorvers wallet and set password = ""
Q_INVOKABLE Wallet * recoveryWallet(const QString &path, const QString &memo, 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 * \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. // THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
import QtQuick 2.2 import QtQuick 2.2
import moneroComponents 1.0 import moneroComponents.WalletManager 1.0
import moneroComponents.Wallet 1.0
import QtQuick.Dialogs 1.2 import QtQuick.Dialogs 1.2
import 'utils.js' as Utils import 'utils.js' as Utils
@ -94,5 +96,6 @@ Item {
wordsTextItem.clipboardButtonVisible: true wordsTextItem.clipboardButtonVisible: true
wordsTextItem.tipTextVisible: true wordsTextItem.tipTextVisible: true
wordsTextItem.memoTextReadOnly: 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>Allow background mining: </b>") + wizard.settings['allow_background_mining'] + "<br>"
+ qsTr("<b>Daemon address: </b>") + wizard.settings['daemon_address'] + "<br>" + qsTr("<b>Daemon address: </b>") + wizard.settings['daemon_address'] + "<br>"
+ qsTr("<b>testnet: </b>") + wizard.settings['testnet'] + "<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 + translationManager.emptyString
return str; return str;
} }

View file

@ -137,6 +137,7 @@ Rectangle {
appWindow.persistentSettings.auto_donations_amount = settings.auto_donations_amount appWindow.persistentSettings.auto_donations_amount = settings.auto_donations_amount
appWindow.persistentSettings.daemon_address = settings.daemon_address appWindow.persistentSettings.daemon_address = settings.daemon_address
appWindow.persistentSettings.testnet = settings.testnet 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. // THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
import QtQuick 2.2 import QtQuick 2.2
import moneroComponents 1.0 import moneroComponents.TranslationManager 1.0
import QtQuick.Dialogs 1.2 import QtQuick.Dialogs 1.2
import QtQuick.Layouts 1.1
import "../components"
// Reusable component for managing wallet (account name, path, private key) // Reusable component for managing wallet (account name, path, private key)
@ -39,6 +41,8 @@ Item {
property alias wordsTextTitle: frameHeader.text property alias wordsTextTitle: frameHeader.text
property alias walletPath: fileUrlInput.text property alias walletPath: fileUrlInput.text
property alias wordsTextItem : memoTextItem property alias wordsTextItem : memoTextItem
property alias restoreHeight : restoreHeightItem.text
property alias restoreHeightVisible: restoreHeightItem.visible
// TODO extend properties if needed // TODO extend properties if needed
@ -112,7 +116,7 @@ Item {
width: 300 width: 300
height: 62 height: 62
TextInput { TextEdit {
id: accountName id: accountName
anchors.fill: parent anchors.fill: parent
horizontalAlignment: TextInput.AlignHCenter horizontalAlignment: TextInput.AlignHCenter
@ -159,10 +163,22 @@ Item {
anchors.topMargin: 16 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 { Row {
anchors.left: parent.left anchors.left: parent.left
anchors.right: parent.right anchors.right: parent.right
anchors.top: memoTextItem.bottom anchors.top: (restoreHeightItem.visible)? restoreHeightItem.bottom : memoTextItem.bottom
anchors.topMargin: 24 anchors.topMargin: 24
spacing: 16 spacing: 16

View file

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

View file

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

View file

@ -27,24 +27,34 @@
// THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. // THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
import QtQuick 2.0 import QtQuick 2.0
import QtQuick.Controls 1.4
import QtQuick.Controls.Styles 1.4
Item { FocusScope {
property alias password: password.text property alias password: password.text
property alias placeholderText: password.placeholderText
signal changed(string password) signal changed(string password)
TextInput { TextField {
id : password id : password
focus:true
anchors.fill: parent anchors.fill: parent
horizontalAlignment: TextInput.AlignHCenter horizontalAlignment: TextInput.AlignLeft
verticalAlignment: TextInput.AlignVCenter verticalAlignment: TextInput.AlignVCenter
font.family: "Arial" font.family: "Arial"
font.pixelSize: 32 font.pixelSize: 32
renderType: Text.NativeRendering
color: "#35B05A"
passwordCharacter: "•"
echoMode: TextInput.Password echoMode: TextInput.Password
focus: true style: TextFieldStyle {
renderType: Text.NativeRendering
textColor: "#35B05A"
passwordCharacter: "•"
background: Rectangle {
radius: 0
border.width: 0
}
}
Keys.onReleased: { Keys.onReleased: {
changed(text) changed(text)
} }

View file

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

View file

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