From 0108947ead4cc9e0ff23fee82db2fb1fd7cb2dad Mon Sep 17 00:00:00 2001 From: David Llewellyn-Jones Date: Sun, 1 Jul 2018 21:48:00 +0100 Subject: [PATCH] Flesh out UI, provide journey data model --- harbour-pedalo.pro | 18 +++- qml/components/InfoRow.qml | 54 ++++++++++ qml/harbour-pedalo.qml | 2 +- qml/pages/{SecondPage.qml => About.qml} | 2 +- qml/pages/AddJourney.qml | 126 ++++++++++++++++++++++++ qml/pages/FirstPage.qml | 43 -------- qml/pages/JourneyList.qml | 45 +++++++++ qml/pages/MainPage.qml | 90 +++++++++++++++++ qml/pages/Stats.qml | 68 +++++++++++++ src/journey.cpp | 52 ++++++++++ src/journey.h | 31 ++++++ src/journeymodel.cpp | 84 ++++++++++++++++ src/journeymodel.h | 45 +++++++++ translations/harbour-pedalo-de.ts | 117 +++++++++++++++++++--- translations/harbour-pedalo.ts | 107 ++++++++++++++++++-- 15 files changed, 816 insertions(+), 68 deletions(-) create mode 100644 qml/components/InfoRow.qml rename qml/pages/{SecondPage.qml => About.qml} (95%) create mode 100644 qml/pages/AddJourney.qml delete mode 100644 qml/pages/FirstPage.qml create mode 100644 qml/pages/JourneyList.qml create mode 100644 qml/pages/MainPage.qml create mode 100644 qml/pages/Stats.qml create mode 100644 src/journey.cpp create mode 100644 src/journey.h create mode 100644 src/journeymodel.cpp create mode 100644 src/journeymodel.h diff --git a/harbour-pedalo.pro b/harbour-pedalo.pro index 62d08f9..cd82343 100644 --- a/harbour-pedalo.pro +++ b/harbour-pedalo.pro @@ -14,18 +14,24 @@ TARGET = harbour-pedalo CONFIG += sailfishapp -SOURCES += src/harbour-pedalo.cpp +SOURCES += src/harbour-pedalo.cpp \ + src/journey.cpp \ + src/journeymodel.cpp DISTFILES += qml/harbour-pedalo.qml \ qml/cover/CoverPage.qml \ - qml/pages/FirstPage.qml \ - qml/pages/SecondPage.qml \ rpm/harbour-pedalo.changes.in \ rpm/harbour-pedalo.changes.run.in \ rpm/harbour-pedalo.spec \ rpm/harbour-pedalo.yaml \ translations/*.ts \ - harbour-pedalo.desktop + harbour-pedalo.desktop \ + qml/pages/MainPage.qml \ + qml/pages/AddJourney.qml \ + qml/pages/Stats.qml \ + qml/pages/JourneyList.qml \ + qml/pages/About.qml \ + qml/components/InfoRow.qml SAILFISHAPP_ICONS = 86x86 108x108 128x128 172x172 @@ -38,3 +44,7 @@ CONFIG += sailfishapp_i18n # following TRANSLATIONS line. And also do not forget to # modify the localized app name in the the .desktop file. TRANSLATIONS += translations/harbour-pedalo-de.ts + +HEADERS += \ + src/journey.h \ + src/journeymodel.h diff --git a/qml/components/InfoRow.qml b/qml/components/InfoRow.qml new file mode 100644 index 0000000..4dce161 --- /dev/null +++ b/qml/components/InfoRow.qml @@ -0,0 +1,54 @@ +import QtQuick 2.0 +import Sailfish.Silica 1.0 + +Item { + id: detailItem + width: parent.width + height: Math.max(labelText.height, valueText.height) + + property alias label: labelText.text + property alias value: valueText.text + property real leftMargin: Theme.horizontalPageMargin + property real rightMargin: Theme.horizontalPageMargin + property real midlineRatio: 0.5 + property real midlineMin: 0.0 + property real midlineMax: width + property real midLine: Math.min(Math.max((width * midlineRatio), midlineMin), midlineMax) + property int pixelSize: Theme.fontSizeSmall + property alias labelTextBold: labelText.font.bold + property alias valueTextBold: valueText.font.bold + + Text { + id: labelText + + y: Theme.paddingSmall + anchors { + left: parent.left + right: parent.right + rightMargin: (width - midLine) + Theme.paddingSmall + leftMargin: detailItem.leftMargin + } + horizontalAlignment: Text.AlignLeft + color: Theme.primaryColor + font.pixelSize: pixelSize + textFormat: Text.PlainText + wrapMode: Text.Wrap + } + + Text { + id: valueText + + y: Theme.paddingSmall + anchors { + left: parent.left + right: parent.right + leftMargin: midLine + Theme.paddingSmall + rightMargin: detailItem.rightMargin + } + horizontalAlignment: Text.AlignLeft + color: Theme.primaryColor + font.pixelSize: pixelSize + textFormat: Text.PlainText + wrapMode: Text.Wrap + } +} diff --git a/qml/harbour-pedalo.qml b/qml/harbour-pedalo.qml index 829cf95..944e65e 100644 --- a/qml/harbour-pedalo.qml +++ b/qml/harbour-pedalo.qml @@ -4,7 +4,7 @@ import "pages" ApplicationWindow { - initialPage: Component { FirstPage { } } + initialPage: Component { MainPage { } } cover: Qt.resolvedUrl("cover/CoverPage.qml") allowedOrientations: defaultAllowedOrientations } diff --git a/qml/pages/SecondPage.qml b/qml/pages/About.qml similarity index 95% rename from qml/pages/SecondPage.qml rename to qml/pages/About.qml index 6dbadf4..669fc47 100644 --- a/qml/pages/SecondPage.qml +++ b/qml/pages/About.qml @@ -12,7 +12,7 @@ Page { model: 20 anchors.fill: parent header: PageHeader { - title: qsTr("Nested Page") + title: qsTr("About") } delegate: BackgroundItem { id: delegate diff --git a/qml/pages/AddJourney.qml b/qml/pages/AddJourney.qml new file mode 100644 index 0000000..b0d8170 --- /dev/null +++ b/qml/pages/AddJourney.qml @@ -0,0 +1,126 @@ +import QtQuick 2.0 +import Sailfish.Silica 1.0 + +Dialog { + id: addJourneyDialog + canAccept: true + property string title: "Add journey" + + // The effective value will be restricted by ApplicationWindow.allowedOrientations + allowedOrientations: Orientation.All + + SilicaFlickable { + id: addJourneyView + anchors.fill: parent + contentHeight: addJourneyColumn.implicitHeight + + VerticalScrollDecorator {} + + Column { + id: addJourneyColumn + spacing: Theme.paddingMedium + width: parent.width + + DialogHeader { + title: addJourneyDialog.title + } + + ValueButton { + id: startDate + function openDateDialog() { + var dialog = pageStack.push("Sailfish.Silica.DatePickerDialog", { + date: value + }) + + dialog.accepted.connect(function() { + value = dialog.dateText + selectedDate = dialog.date + }) + } + + label: "Date" + value: Qt.formatDate(new Date(), 'd MMM yyyy') + width: parent.width + onClicked: openDateDialog() + } + + ValueButton { + id: startTime + property date time: new Date() + label: qsTr("Start time") + value: Qt.formatTime(time, 'hh:mm') + width: parent.width + onClicked: { + console.log("Hours: " + time.getHours()) + console.log("Mins: " + time.getMinutes()) + var dialog = pageStack.push("Sailfish.Silica.TimePickerDialog", { hour: time.getHours(), minute: time.getMinutes()}) + dialog.accepted.connect(function() { + time = new Date(0, 0, 0, dialog.hour, dialog.minute) + }) + } + onTimeChanged: { + value = Qt.formatTime(time, 'hh:mm') + endTime.time = new Date(0, 0, 0, startTime.time.getHours() + durationTime.duration.getHours(), startTime.time.getMinutes() + durationTime.duration.getMinutes()) + } + } + + ValueButton { + id: endTime + property date time: new Date() + label: qsTr("End time") + value: Qt.formatTime(time, 'hh:mm') + width: parent.width + onClicked: { + var dialog = pageStack.push("Sailfish.Silica.TimePickerDialog", { hour: time.getHours(), minute: time.getMinutes()}) + dialog.accepted.connect(function() { + time = new Date(0, 0, 0, dialog.hour, dialog.minute) + }) + } + onTimeChanged: { + value = Qt.formatTime(time, 'hh:mm') + durationTime.duration = new Date(0, 0, 0, endTime.time.getHours() - startTime.time.getHours(), endTime.time.getMinutes() - startTime.time.getMinutes()) + } + } + + ValueButton { + id: durationTime + property date duration: new Date(0, 0, 0, 0, 0) + label: qsTr("Duration") + value: Qt.formatTime(duration, 'hh:mm') + width: parent.width + onClicked: { + var dialog = pageStack.push("Sailfish.Silica.TimePickerDialog", { hour: duration.getHours(), minute: duration.getMinutes()}) + dialog.accepted.connect(function() { + duration = new Date(0, 0, 0, dialog.hour, dialog.minute) + }) + } + onDurationChanged: { + value = Qt.formatTime(duration, 'hh:mm') + endTime.time = new Date(0, 0, 0, startTime.time.getHours() + durationTime.duration.getHours(), startTime.time.getMinutes() + durationTime.duration.getMinutes()) + } + } + + TextField { + id: faster + width: parent.width + inputMethodHints: Qt.ImhDigitsOnly + label: qsTr("Cycles which you overtook") + placeholderText: label + horizontalAlignment: TextInput.AlignLeft + EnterKey.iconSource: "image://theme/icon-m-enter-next" + EnterKey.onClicked: slower.focus = true + } + + TextField { + id: slower + width: parent.width + inputMethodHints: Qt.ImhDigitsOnly + label: qsTr("Cycles which overtook you") + placeholderText: label + horizontalAlignment: TextInput.AlignLeft + EnterKey.iconSource: "image://theme/icon-m-enter-next" + EnterKey.onClicked: addJourneyDialog.accept() + } + } + } +} diff --git a/qml/pages/FirstPage.qml b/qml/pages/FirstPage.qml deleted file mode 100644 index 447accf..0000000 --- a/qml/pages/FirstPage.qml +++ /dev/null @@ -1,43 +0,0 @@ -import QtQuick 2.0 -import Sailfish.Silica 1.0 - -Page { - id: page - - // The effective value will be restricted by ApplicationWindow.allowedOrientations - allowedOrientations: Orientation.All - - // To enable PullDownMenu, place our content in a SilicaFlickable - SilicaFlickable { - anchors.fill: parent - - // PullDownMenu and PushUpMenu must be declared in SilicaFlickable, SilicaListView or SilicaGridView - PullDownMenu { - MenuItem { - text: qsTr("Show Page 2") - onClicked: pageStack.push(Qt.resolvedUrl("SecondPage.qml")) - } - } - - // Tell SilicaFlickable the height of its content. - contentHeight: column.height - - // Place our content in a Column. The PageHeader is always placed at the top - // of the page, followed by our content. - Column { - id: column - - width: page.width - spacing: Theme.paddingLarge - PageHeader { - title: qsTr("UI Template") - } - Label { - x: Theme.horizontalPageMargin - text: qsTr("Hello Sailors") - color: Theme.secondaryHighlightColor - font.pixelSize: Theme.fontSizeExtraLarge - } - } - } -} diff --git a/qml/pages/JourneyList.qml b/qml/pages/JourneyList.qml new file mode 100644 index 0000000..9d84129 --- /dev/null +++ b/qml/pages/JourneyList.qml @@ -0,0 +1,45 @@ +import QtQuick 2.0 +import Sailfish.Silica 1.0 + +Page { + id: page + + // The effective value will be restricted by ApplicationWindow.allowedOrientations + allowedOrientations: Orientation.All + property int columnwidth: page.width - 2 * Theme.horizontalPageMargin + + SilicaListView { + id: listView + model: 20 + anchors.fill: parent + header: PageHeader { + title: qsTr("Journey list") + } + delegate: BackgroundItem { + id: delegate + + Row { + spacing: Theme.paddingLarge + x: Theme.horizontalPageMargin + + Label { + width: columnwidth / 3.0 + text: qsTr("Item") + " " + index + color: delegate.highlighted ? Theme.highlightColor : Theme.primaryColor + } + Label { + width: columnwidth / 3.0 + text: qsTr("1 May 2018") + color: delegate.highlighted ? Theme.highlightColor : Theme.primaryColor + } + Label { + width: columnwidth / 3.0 + text: qsTr("20 mins") + color: delegate.highlighted ? Theme.highlightColor : Theme.primaryColor + } + } + onClicked: pageStack.push(Qt.resolvedUrl("AddJourney.qml"), {title: "Edit journey"}) + } + VerticalScrollDecorator {} + } +} diff --git a/qml/pages/MainPage.qml b/qml/pages/MainPage.qml new file mode 100644 index 0000000..31b1447 --- /dev/null +++ b/qml/pages/MainPage.qml @@ -0,0 +1,90 @@ +import QtQuick 2.0 +import Sailfish.Silica 1.0 + +Page { + id: page + property bool cycling: false + + // The effective value will be restricted by ApplicationWindow.allowedOrientations + allowedOrientations: Orientation.All + + // To enable PullDownMenu, place our content in a SilicaFlickable + SilicaFlickable { + anchors.fill: parent + + // PullDownMenu and PushUpMenu must be declared in SilicaFlickable, SilicaListView or SilicaGridView + PullDownMenu { + MenuItem { + text: qsTr("Show Page 2") + onClicked: pageStack.push(Qt.resolvedUrl("SecondPage.qml")) + } + } + + // Tell SilicaFlickable the height of its content. + contentHeight: column.height + + // Place our content in a Column. The PageHeader is always placed at the top + // of the page, followed by our content. + Column { + id: column + + width: page.width + spacing: Theme.paddingLarge + PageHeader { + title: qsTr("Pedalo") + } + + SectionHeader { + text: qsTr("Cycle!") + } + + Button { + anchors.horizontalCenter: parent.horizontalCenter + text: cycling ? qsTr("Finish") : qsTr("Start a journey") + onClicked: { + if (cycling) { + var dialog = pageStack.push(Qt.resolvedUrl("AddJourney.qml")) + + dialog.accepted.connect(function() { + cycling = false + }) + } + else { + cycling = true + } + } + } + + SectionHeader { + text: qsTr("Add a journey") + } + + Button { + anchors.horizontalCenter: parent.horizontalCenter + text: qsTr("Enter journey") + onClicked: pageStack.push(Qt.resolvedUrl("AddJourney.qml")) + } + + SectionHeader { + text: qsTr("Latest stats") + } + + Button { + anchors.horizontalCenter: parent.horizontalCenter + text: qsTr("View stats") + onClicked: pageStack.push(Qt.resolvedUrl("Stats.qml")) + } + + SectionHeader { + text: qsTr("Previous journeys") + } + + Button { + anchors.horizontalCenter: parent.horizontalCenter + text: qsTr("View journeys") + onClicked: pageStack.push(Qt.resolvedUrl("JourneyList.qml")) + } + + } + } +} diff --git a/qml/pages/Stats.qml b/qml/pages/Stats.qml new file mode 100644 index 0000000..f6e319c --- /dev/null +++ b/qml/pages/Stats.qml @@ -0,0 +1,68 @@ +import QtQuick 2.0 +import Sailfish.Silica 1.0 +import "../components" + +Page { + id: statsPage + + // The effective value will be restricted by ApplicationWindow.allowedOrientations + allowedOrientations: Orientation.All + + SilicaFlickable { + id: statsView + anchors.fill: parent + contentHeight: statsColumn.implicitHeight + + VerticalScrollDecorator {} + + Column { + id: statsColumn + spacing: Theme.paddingLarge + width: parent.width + + PageHeader { + title: qsTr("Stats") + } + + InfoRow { + label: qsTr("Journeys:") + value: "0" + midlineRatio: 0.7 + midlineMin: Theme.fontSizeSmall * 10 + midlineMax: Theme.fontSizeSmall * 15 + pixelSize: Theme.fontSizeMedium + labelTextBold: true + } + + InfoRow { + label: qsTr("Time spent cycling:") + value: "0" + midlineRatio: 0.7 + midlineMin: Theme.fontSizeSmall * 10 + midlineMax: Theme.fontSizeSmall * 15 + pixelSize: Theme.fontSizeMedium + labelTextBold: true + } + + InfoRow { + label: qsTr("Average journey duration:") + value: "0" + midlineRatio: 0.7 + midlineMin: Theme.fontSizeSmall * 10 + midlineMax: Theme.fontSizeSmall * 15 + pixelSize: Theme.fontSizeMedium + labelTextBold: true + } + + InfoRow { + label: qsTr("Speed percentile:") + value: "0%" + midlineRatio: 0.7 + midlineMin: Theme.fontSizeSmall * 10 + midlineMax: Theme.fontSizeSmall * 15 + pixelSize: Theme.fontSizeMedium + labelTextBold: true + } + } + } +} diff --git a/src/journey.cpp b/src/journey.cpp new file mode 100644 index 0000000..af13ec8 --- /dev/null +++ b/src/journey.cpp @@ -0,0 +1,52 @@ +#include "journey.h" +#include + +Journey::Journey() : + start(0u), + duration(0u), + overtook(0u), + overtakenby(0u) +{ +} + +Journey::Journey(quint64 start, quint32 duration, quint32 overtook, quint32 overtakenby) : + start(start), + duration(duration), + overtook(overtook), + overtakenby(overtakenby) +{ +} + +quint64 Journey::getStart () const { + return start; +} + +qint32 Journey::getDuration () const { + return duration; +} + +qint32 Journey::getOvertook () const { + return overtook; +} + +qint32 Journey::getOvertakenBy () const { + return overtakenby; +} + + +void Journey::setStart (const quint64 value) { + start = value; +} + +void Journey::setDuration (qint32 value) { + duration = value; +} + +void Journey::setOvertook (qint32 value) { + overtook = value; +} + +void Journey::setOvertakenBy (qint32 value) { + overtakenby = value; +} + diff --git a/src/journey.h b/src/journey.h new file mode 100644 index 0000000..45393b3 --- /dev/null +++ b/src/journey.h @@ -0,0 +1,31 @@ +#ifndef JOURNEY_H +#define JOURNEY_H + +#include +#include + +class Journey +{ +public: + Journey(); + Journey(quint64 start, quint32 duration, quint32 overtook, quint32 overtakenby); + + quint64 getStart () const; + qint32 getDuration () const; + qint32 getOvertook () const; + qint32 getOvertakenBy () const; + + void setStart (const quint64 value); + void setDuration (qint32 value); + void setOvertook (qint32 value); + void setOvertakenBy (qint32 value); + +private: + quint64 start; + quint32 duration; + quint32 overtook; + quint32 overtakenby; +}; + + +#endif // JOURNEY_H diff --git a/src/journeymodel.cpp b/src/journeymodel.cpp new file mode 100644 index 0000000..18f83c2 --- /dev/null +++ b/src/journeymodel.cpp @@ -0,0 +1,84 @@ +#include "journeymodel.h" +#include + +JourneyModel::JourneyModel(QObject *parent) : QAbstractListModel(parent) { + roles[StartRole] = "start"; + roles[DurationRole] = "duration"; + roles[OvertookRole] = "overtook"; + roles[OvertakenByRole] = "overtakenby"; +} + +QHash JourneyModel::roleNames() const { + return roles; +} + +void JourneyModel::addJourney(const Journey &journey) +{ + beginInsertRows(QModelIndex(), rowCount(), rowCount()); + journeys << journey; + endInsertRows(); +} + +int JourneyModel::rowCount(const QModelIndex & parent) const { + Q_UNUSED(parent) + return journeys.count(); +} + +QVariant JourneyModel::data(const QModelIndex & index, int role) const { + if (index.row() < 0 || index.row() > journeys.count()) + return QVariant(); + + const Journey &journey = journeys[index.row()]; + if (role == StartRole) + return journey.getStart(); + else if (role == DurationRole) + return journey.getDuration(); + else if (role == OvertookRole) + return journey.getOvertook(); + else if (role == OvertakenByRole) + return journey.getOvertakenBy(); + return QVariant(); +} + +void JourneyModel::clear() { + journeys.clear(); +} + +void JourneyModel::exportToFile(QFile & file) { + if (file.open(QIODevice::WriteOnly)) { + QTextStream out(&file); + for (QList::iterator journeyIter = journeys.begin(); journeyIter != journeys.end(); journeyIter++) { + out << journeyIter->getStart() << ","; + out << journeyIter->getDuration() << ","; + out << journeyIter->getOvertook() << ","; + out << journeyIter->getOvertakenBy() << endl; + } + file.close(); + } +} + +void JourneyModel::importFromFile(QFile & file) { + if (file.open(QIODevice::ReadOnly)) { + QTextStream in(&file); + while (!in.atEnd()) { + QStringList data; + quint64 start; + qint32 duration = 0; + qint32 overtook = 0; + qint32 overtakenby = 0; + + data = in.readLine().split(","); + if (data.length() == 4) { + start = data[0].toLongLong(); + duration = data[1].toLongLong(); + overtook = data[2].toLongLong(); + overtakenby = data[3].toLongLong(); + + addJourney(Journey(start, duration, overtook, overtakenby)); + } + } + file.close(); + } +} + + diff --git a/src/journeymodel.h b/src/journeymodel.h new file mode 100644 index 0000000..36d4a92 --- /dev/null +++ b/src/journeymodel.h @@ -0,0 +1,45 @@ +#ifndef JOURNEYMODEL_H +#define JOURNEYMODEL_H + +#include +#include +#include + +#include "journey.h" + +class JourneyModel : public QAbstractListModel +{ + Q_OBJECT +public: + enum JourneyRoles { + StartRole = Qt::UserRole + 1, + DurationRole, + OvertookRole, + OvertakenByRole + }; + + QHash roleNames() const; + + JourneyModel(QObject *parent = 0); + + void addJourney(const Journey &journey); + + int rowCount(const QModelIndex & parent = QModelIndex()) const; + + QVariant data(const QModelIndex & index, int role = Qt::DisplayRole) const; + + void clear(); + + void exportToFile(QFile & file); + void importFromFile(QFile & file); + +signals: + // General signals + void journeysChanged(); + +private: + QHash roles; + QList journeys; +}; + +#endif // JOURNEYMODEL_H diff --git a/translations/harbour-pedalo-de.ts b/translations/harbour-pedalo-de.ts index 7dbef71..acb00bb 100644 --- a/translations/harbour-pedalo-de.ts +++ b/translations/harbour-pedalo-de.ts @@ -1,6 +1,44 @@ - + + + About + + About + + + + Item + Element + + + + AddJourney + + Add journey + + + + Start time + + + + End time + + + + Duration + + + + Cycles which you overtook + + + + Cycles which overtook you + + + CoverPage @@ -9,29 +47,84 @@ - FirstPage + JourneyList + + Item + Element + + + Journey list + + + + + MainPage Show Page 2 - Zur Seite 2 + Zur Seite 2 + + + Pedalo + - UI Template - UI-Vorlage + Cycle! + - Hello Sailors - Hallo Matrosen + Start a journey + + + + Add a journey + + + + Enter journey + + + + Latest stats + + + + View stats + + + + Previous journeys + + + + View journeys + + + + Finish + - SecondPage + Stats - Nested Page - Unterseite + Stats + - Item - Element + Time spent cycling + + + + Average journey duration + + + + Speed percentile + + + + Journeys: + diff --git a/translations/harbour-pedalo.ts b/translations/harbour-pedalo.ts index 0374c0e..2b821ce 100644 --- a/translations/harbour-pedalo.ts +++ b/translations/harbour-pedalo.ts @@ -1,6 +1,44 @@ - + + + About + + About + + + + Item + + + + + AddJourney + + Add journey + + + + Start time + + + + End time + + + + Duration + + + + Cycles which you overtook + + + + Cycles which overtook you + + + CoverPage @@ -9,28 +47,83 @@ - FirstPage + JourneyList + + Item + + + + Journey list + + + + + MainPage Show Page 2 - UI Template + Pedalo + + + + Cycle! + + + + Start a journey + + + + Add a journey + + + + Enter journey + + + + Latest stats + + + + View stats + + + + Previous journeys - Hello Sailors + View journeys + + + + Finish - SecondPage + Stats - Nested Page + Stats - Item + Time spent cycling + + + + Average journey duration + + + + Speed percentile + + + + Journeys: -- 2.25.1