Flesh out UI, provide journey data model
authorDavid Llewellyn-Jones <david@flypig.co.uk>
Sun, 1 Jul 2018 20:48:00 +0000 (21:48 +0100)
committerDavid Llewellyn-Jones <david@flypig.co.uk>
Sun, 1 Jul 2018 20:48:00 +0000 (21:48 +0100)
16 files changed:
harbour-pedalo.pro
qml/components/InfoRow.qml [new file with mode: 0644]
qml/harbour-pedalo.qml
qml/pages/About.qml [new file with mode: 0644]
qml/pages/AddJourney.qml [new file with mode: 0644]
qml/pages/FirstPage.qml [deleted file]
qml/pages/JourneyList.qml [new file with mode: 0644]
qml/pages/MainPage.qml [new file with mode: 0644]
qml/pages/SecondPage.qml [deleted file]
qml/pages/Stats.qml [new file with mode: 0644]
src/journey.cpp [new file with mode: 0644]
src/journey.h [new file with mode: 0644]
src/journeymodel.cpp [new file with mode: 0644]
src/journeymodel.h [new file with mode: 0644]
translations/harbour-pedalo-de.ts
translations/harbour-pedalo.ts

index 62d08f9..cd82343 100644 (file)
@@ -14,18 +14,24 @@ TARGET = harbour-pedalo
 
 CONFIG += sailfishapp
 
 
 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 \
 
 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 \
     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
 
 
 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
 # 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 (file)
index 0000000..4dce161
--- /dev/null
@@ -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
+    }
+}
index 829cf95..944e65e 100644 (file)
@@ -4,7 +4,7 @@ import "pages"
 
 ApplicationWindow
 {
 
 ApplicationWindow
 {
-    initialPage: Component { FirstPage { } }
+    initialPage: Component { MainPage { } }
     cover: Qt.resolvedUrl("cover/CoverPage.qml")
     allowedOrientations: defaultAllowedOrientations
 }
     cover: Qt.resolvedUrl("cover/CoverPage.qml")
     allowedOrientations: defaultAllowedOrientations
 }
diff --git a/qml/pages/About.qml b/qml/pages/About.qml
new file mode 100644 (file)
index 0000000..669fc47
--- /dev/null
@@ -0,0 +1,30 @@
+import QtQuick 2.0
+import Sailfish.Silica 1.0
+
+Page {
+    id: page
+
+    // The effective value will be restricted by ApplicationWindow.allowedOrientations
+    allowedOrientations: Orientation.All
+
+    SilicaListView {
+        id: listView
+        model: 20
+        anchors.fill: parent
+        header: PageHeader {
+            title: qsTr("About")
+        }
+        delegate: BackgroundItem {
+            id: delegate
+
+            Label {
+                x: Theme.horizontalPageMargin
+                text: qsTr("Item") + " " + index
+                anchors.verticalCenter: parent.verticalCenter
+                color: delegate.highlighted ? Theme.highlightColor : Theme.primaryColor
+            }
+            onClicked: console.log("Clicked " + index)
+        }
+        VerticalScrollDecorator {}
+    }
+}
diff --git a/qml/pages/AddJourney.qml b/qml/pages/AddJourney.qml
new file mode 100644 (file)
index 0000000..b0d8170
--- /dev/null
@@ -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 (file)
index 447accf..0000000
+++ /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 (file)
index 0000000..9d84129
--- /dev/null
@@ -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 (file)
index 0000000..31b1447
--- /dev/null
@@ -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/SecondPage.qml b/qml/pages/SecondPage.qml
deleted file mode 100644 (file)
index 6dbadf4..0000000
+++ /dev/null
@@ -1,30 +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
-
-    SilicaListView {
-        id: listView
-        model: 20
-        anchors.fill: parent
-        header: PageHeader {
-            title: qsTr("Nested Page")
-        }
-        delegate: BackgroundItem {
-            id: delegate
-
-            Label {
-                x: Theme.horizontalPageMargin
-                text: qsTr("Item") + " " + index
-                anchors.verticalCenter: parent.verticalCenter
-                color: delegate.highlighted ? Theme.highlightColor : Theme.primaryColor
-            }
-            onClicked: console.log("Clicked " + index)
-        }
-        VerticalScrollDecorator {}
-    }
-}
diff --git a/qml/pages/Stats.qml b/qml/pages/Stats.qml
new file mode 100644 (file)
index 0000000..f6e319c
--- /dev/null
@@ -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 (file)
index 0000000..af13ec8
--- /dev/null
@@ -0,0 +1,52 @@
+#include "journey.h"
+#include <QDebug>
+
+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 (file)
index 0000000..45393b3
--- /dev/null
@@ -0,0 +1,31 @@
+#ifndef JOURNEY_H
+#define JOURNEY_H
+
+#include <QObject>
+#include <QTime>
+
+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 (file)
index 0000000..18f83c2
--- /dev/null
@@ -0,0 +1,84 @@
+#include "journeymodel.h"
+#include <QDebug>
+
+JourneyModel::JourneyModel(QObject *parent) : QAbstractListModel(parent) {
+    roles[StartRole] = "start";
+    roles[DurationRole] = "duration";
+    roles[OvertookRole] = "overtook";
+    roles[OvertakenByRole] = "overtakenby";
+}
+
+QHash<int, QByteArray> 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<Journey>::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 (file)
index 0000000..36d4a92
--- /dev/null
@@ -0,0 +1,45 @@
+#ifndef JOURNEYMODEL_H
+#define JOURNEYMODEL_H
+
+#include <QAbstractListModel>
+#include <QStringList>
+#include <QFile>
+
+#include "journey.h"
+
+class JourneyModel : public QAbstractListModel
+{
+    Q_OBJECT
+public:
+    enum JourneyRoles {
+        StartRole = Qt::UserRole + 1,
+        DurationRole,
+        OvertookRole,
+        OvertakenByRole
+    };
+
+    QHash<int, QByteArray> 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<int, QByteArray> roles;
+    QList<Journey> journeys;
+};
+
+#endif // JOURNEYMODEL_H
index 7dbef71..acb00bb 100644 (file)
@@ -1,6 +1,44 @@
 <?xml version="1.0" encoding="utf-8"?>
 <!DOCTYPE TS>
 <?xml version="1.0" encoding="utf-8"?>
 <!DOCTYPE TS>
-<TS version="2.0">
+<TS version="2.1">
+<context>
+    <name>About</name>
+    <message>
+        <source>About</source>
+        <translation type="unfinished"></translation>
+    </message>
+    <message>
+        <source>Item</source>
+        <translation type="unfinished">Element</translation>
+    </message>
+</context>
+<context>
+    <name>AddJourney</name>
+    <message>
+        <source>Add journey</source>
+        <translation type="unfinished"></translation>
+    </message>
+    <message>
+        <source>Start time</source>
+        <translation type="unfinished"></translation>
+    </message>
+    <message>
+        <source>End time</source>
+        <translation type="unfinished"></translation>
+    </message>
+    <message>
+        <source>Duration</source>
+        <translation type="unfinished"></translation>
+    </message>
+    <message>
+        <source>Cycles which you overtook</source>
+        <translation type="unfinished"></translation>
+    </message>
+    <message>
+        <source>Cycles which overtook you</source>
+        <translation type="unfinished"></translation>
+    </message>
+</context>
 <context>
     <name>CoverPage</name>
     <message>
 <context>
     <name>CoverPage</name>
     <message>
     </message>
 </context>
 <context>
     </message>
 </context>
 <context>
-    <name>FirstPage</name>
+    <name>JourneyList</name>
+    <message>
+        <source>Item</source>
+        <translation type="unfinished">Element</translation>
+    </message>
+    <message>
+        <source>Journey list</source>
+        <translation type="unfinished"></translation>
+    </message>
+</context>
+<context>
+    <name>MainPage</name>
     <message>
         <source>Show Page 2</source>
     <message>
         <source>Show Page 2</source>
-        <translation>Zur Seite 2</translation>
+        <translation type="unfinished">Zur Seite 2</translation>
+    </message>
+    <message>
+        <source>Pedalo</source>
+        <translation type="unfinished"></translation>
     </message>
     <message>
     </message>
     <message>
-        <source>UI Template</source>
-        <translation>UI-Vorlage</translation>
+        <source>Cycle!</source>
+        <translation type="unfinished"></translation>
     </message>
     <message>
     </message>
     <message>
-        <source>Hello Sailors</source>
-        <translation>Hallo Matrosen</translation>
+        <source>Start a journey</source>
+        <translation type="unfinished"></translation>
+    </message>
+    <message>
+        <source>Add a journey</source>
+        <translation type="unfinished"></translation>
+    </message>
+    <message>
+        <source>Enter journey</source>
+        <translation type="unfinished"></translation>
+    </message>
+    <message>
+        <source>Latest stats</source>
+        <translation type="unfinished"></translation>
+    </message>
+    <message>
+        <source>View stats</source>
+        <translation type="unfinished"></translation>
+    </message>
+    <message>
+        <source>Previous journeys</source>
+        <translation type="unfinished"></translation>
+    </message>
+    <message>
+        <source>View journeys</source>
+        <translation type="unfinished"></translation>
+    </message>
+    <message>
+        <source>Finish</source>
+        <translation type="unfinished"></translation>
     </message>
 </context>
 <context>
     </message>
 </context>
 <context>
-    <name>SecondPage</name>
+    <name>Stats</name>
     <message>
     <message>
-        <source>Nested Page</source>
-        <translation>Unterseite</translation>
+        <source>Stats</source>
+        <translation type="unfinished"></translation>
     </message>
     <message>
     </message>
     <message>
-        <source>Item</source>
-        <translation>Element</translation>
+        <source>Time spent cycling</source>
+        <translation type="unfinished"></translation>
+    </message>
+    <message>
+        <source>Average journey duration</source>
+        <translation type="unfinished"></translation>
+    </message>
+    <message>
+        <source>Speed percentile</source>
+        <translation type="unfinished"></translation>
+    </message>
+    <message>
+        <source>Journeys:</source>
+        <translation type="unfinished"></translation>
     </message>
 </context>
 </TS>
     </message>
 </context>
 </TS>
index 0374c0e..2b821ce 100644 (file)
@@ -1,6 +1,44 @@
 <?xml version="1.0" encoding="utf-8"?>
 <!DOCTYPE TS>
 <?xml version="1.0" encoding="utf-8"?>
 <!DOCTYPE TS>
-<TS version="2.0">
+<TS version="2.1">
+<context>
+    <name>About</name>
+    <message>
+        <source>About</source>
+        <translation type="unfinished"></translation>
+    </message>
+    <message>
+        <source>Item</source>
+        <translation type="unfinished"></translation>
+    </message>
+</context>
+<context>
+    <name>AddJourney</name>
+    <message>
+        <source>Add journey</source>
+        <translation type="unfinished"></translation>
+    </message>
+    <message>
+        <source>Start time</source>
+        <translation type="unfinished"></translation>
+    </message>
+    <message>
+        <source>End time</source>
+        <translation type="unfinished"></translation>
+    </message>
+    <message>
+        <source>Duration</source>
+        <translation type="unfinished"></translation>
+    </message>
+    <message>
+        <source>Cycles which you overtook</source>
+        <translation type="unfinished"></translation>
+    </message>
+    <message>
+        <source>Cycles which overtook you</source>
+        <translation type="unfinished"></translation>
+    </message>
+</context>
 <context>
     <name>CoverPage</name>
     <message>
 <context>
     <name>CoverPage</name>
     <message>
     </message>
 </context>
 <context>
     </message>
 </context>
 <context>
-    <name>FirstPage</name>
+    <name>JourneyList</name>
+    <message>
+        <source>Item</source>
+        <translation type="unfinished"></translation>
+    </message>
+    <message>
+        <source>Journey list</source>
+        <translation type="unfinished"></translation>
+    </message>
+</context>
+<context>
+    <name>MainPage</name>
     <message>
         <source>Show Page 2</source>
         <translation type="unfinished"></translation>
     </message>
     <message>
     <message>
         <source>Show Page 2</source>
         <translation type="unfinished"></translation>
     </message>
     <message>
-        <source>UI Template</source>
+        <source>Pedalo</source>
+        <translation type="unfinished"></translation>
+    </message>
+    <message>
+        <source>Cycle!</source>
+        <translation type="unfinished"></translation>
+    </message>
+    <message>
+        <source>Start a journey</source>
+        <translation type="unfinished"></translation>
+    </message>
+    <message>
+        <source>Add a journey</source>
+        <translation type="unfinished"></translation>
+    </message>
+    <message>
+        <source>Enter journey</source>
+        <translation type="unfinished"></translation>
+    </message>
+    <message>
+        <source>Latest stats</source>
+        <translation type="unfinished"></translation>
+    </message>
+    <message>
+        <source>View stats</source>
+        <translation type="unfinished"></translation>
+    </message>
+    <message>
+        <source>Previous journeys</source>
         <translation type="unfinished"></translation>
     </message>
     <message>
         <translation type="unfinished"></translation>
     </message>
     <message>
-        <source>Hello Sailors</source>
+        <source>View journeys</source>
+        <translation type="unfinished"></translation>
+    </message>
+    <message>
+        <source>Finish</source>
         <translation type="unfinished"></translation>
     </message>
 </context>
 <context>
         <translation type="unfinished"></translation>
     </message>
 </context>
 <context>
-    <name>SecondPage</name>
+    <name>Stats</name>
     <message>
     <message>
-        <source>Nested Page</source>
+        <source>Stats</source>
         <translation type="unfinished"></translation>
     </message>
     <message>
         <translation type="unfinished"></translation>
     </message>
     <message>
-        <source>Item</source>
+        <source>Time spent cycling</source>
+        <translation type="unfinished"></translation>
+    </message>
+    <message>
+        <source>Average journey duration</source>
+        <translation type="unfinished"></translation>
+    </message>
+    <message>
+        <source>Speed percentile</source>
+        <translation type="unfinished"></translation>
+    </message>
+    <message>
+        <source>Journeys:</source>
         <translation type="unfinished"></translation>
     </message>
 </context>
         <translation type="unfinished"></translation>
     </message>
 </context>