Add flashy spiral timer to cover
authorDavid Llewellyn-Jones <david@flypig.co.uk>
Mon, 16 Jul 2018 22:29:40 +0000 (23:29 +0100)
committerDavid Llewellyn-Jones <david@flypig.co.uk>
Mon, 16 Jul 2018 22:29:40 +0000 (23:29 +0100)
generate.sh
harbour-pedalo.pro
inputs/icon-cover-journey-finish.svg [new file with mode: 0644]
inputs/icon-cover-journey-start.svg [new file with mode: 0644]
qml/cover/ClockView.qml [new file with mode: 0644]
qml/cover/CoverPage.qml
qml/cover/LargeItem.qml [new file with mode: 0644]
qml/harbour-pedalo.qml
translations/harbour-pedalo-de.ts
translations/harbour-pedalo.ts

index 7233e82..fcff0a3 100755 (executable)
@@ -36,3 +36,6 @@ generate 303 86 "pedalo-title"
 # Generate buttons
 generate 246 200 "button-journey-add button-journey-start button-journey-finish button-list button-stats"
 
+# Generate cover action icons
+generate 32 32 "icon-cover-journey-finish icon-cover-journey-start"
+
index 0b88acc..ce9b2c7 100644 (file)
@@ -50,7 +50,9 @@ DISTFILES += qml/harbour-pedalo.qml \
     qml/components/InfoRow.qml \
     qml/pages/JourneyEdit.qml \
     qml/pages/JourneyInfo.qml \
-    qml/components/BarButton.qml
+    qml/components/BarButton.qml \
+    qml/cover/ClockView.qml \
+    qml/cover/LargeItem.qml
 
 SAILFISHAPP_ICONS = 86x86 108x108 128x128 172x172 256x256
 
diff --git a/inputs/icon-cover-journey-finish.svg b/inputs/icon-cover-journey-finish.svg
new file mode 100644 (file)
index 0000000..7741437
--- /dev/null
@@ -0,0 +1,79 @@
+<?xml version="1.0" encoding="UTF-8" standalone="no"?>
+<!-- Created with Inkscape (http://www.inkscape.org/) -->
+
+<svg
+   xmlns:dc="http://purl.org/dc/elements/1.1/"
+   xmlns:cc="http://creativecommons.org/ns#"
+   xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
+   xmlns:svg="http://www.w3.org/2000/svg"
+   xmlns="http://www.w3.org/2000/svg"
+   xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
+   xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
+   width="31.999998"
+   height="31.999998"
+   viewBox="0 0 31.999999 31.999999"
+   id="svg12804"
+   version="1.1"
+   inkscape:version="0.91 r13725"
+   sodipodi:docname="icon-cover-journey-finish.svg">
+  <defs
+     id="defs12806" />
+  <sodipodi:namedview
+     id="base"
+     pagecolor="#1f1f1f"
+     bordercolor="#666666"
+     borderopacity="1.0"
+     inkscape:pageopacity="0"
+     inkscape:pageshadow="2"
+     inkscape:zoom="7.9195959"
+     inkscape:cx="-23.249276"
+     inkscape:cy="19.596642"
+     inkscape:document-units="px"
+     inkscape:current-layer="layer1"
+     showgrid="false"
+     fit-margin-top="0"
+     fit-margin-left="0"
+     fit-margin-right="0"
+     fit-margin-bottom="0"
+     units="px"
+     inkscape:window-width="3200"
+     inkscape:window-height="1773"
+     inkscape:window-x="0"
+     inkscape:window-y="27"
+     inkscape:window-maximized="1" />
+  <metadata
+     id="metadata12809">
+    <rdf:RDF>
+      <cc:Work
+         rdf:about="">
+        <dc:format>image/svg+xml</dc:format>
+        <dc:type
+           rdf:resource="http://purl.org/dc/dcmitype/StillImage" />
+        <dc:title />
+      </cc:Work>
+    </rdf:RDF>
+  </metadata>
+  <g
+     inkscape:label="Layer 1"
+     inkscape:groupmode="layer"
+     id="layer1"
+     transform="translate(-355.42857,-436.3622)">
+    <g
+       transform="translate(189.79905,-376.76244)"
+       id="g8251">
+      <rect
+         height="32"
+         width="32"
+         style="opacity:0;fill:#ffffff"
+         id="icon-cover-pause_3_-5-2-3"
+         x="165.62952"
+         y="813.12463" />
+      <path
+         sodipodi:nodetypes="cssccsscc"
+         inkscape:connector-curvature="0"
+         d="m 191.33889,841.84461 c 1.65,0 3,-1.35 3,-3 l 0,-19.439 c 0,-1.65 -1.35,-3 -3,-3 l -19.41875,0 c -1.65,0 -3,1.35 -3,3 l 0,19.438 c 0,1.65 1.35,3 3,3 z"
+         style="fill:#ffffff;stroke:none"
+         id="path8256-0-6" />
+    </g>
+  </g>
+</svg>
diff --git a/inputs/icon-cover-journey-start.svg b/inputs/icon-cover-journey-start.svg
new file mode 100644 (file)
index 0000000..e754855
--- /dev/null
@@ -0,0 +1,78 @@
+<?xml version="1.0" encoding="UTF-8" standalone="no"?>
+<!-- Created with Inkscape (http://www.inkscape.org/) -->
+
+<svg
+   xmlns:dc="http://purl.org/dc/elements/1.1/"
+   xmlns:cc="http://creativecommons.org/ns#"
+   xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
+   xmlns:svg="http://www.w3.org/2000/svg"
+   xmlns="http://www.w3.org/2000/svg"
+   xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
+   xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
+   width="31.999998"
+   height="31.999998"
+   viewBox="0 0 31.999999 31.999999"
+   id="svg8283"
+   version="1.1"
+   inkscape:version="0.91 r13725"
+   sodipodi:docname="icon-cover-journey-start.svg">
+  <defs
+     id="defs8285" />
+  <sodipodi:namedview
+     id="base"
+     pagecolor="#1f1f1f"
+     bordercolor="#666666"
+     borderopacity="1.0"
+     inkscape:pageopacity="0"
+     inkscape:pageshadow="2"
+     inkscape:zoom="7.9195959"
+     inkscape:cx="5.0803359"
+     inkscape:cy="0.64592393"
+     inkscape:document-units="px"
+     inkscape:current-layer="layer1"
+     showgrid="false"
+     fit-margin-top="0"
+     fit-margin-left="0"
+     fit-margin-right="0"
+     fit-margin-bottom="0"
+     units="px"
+     inkscape:window-width="3200"
+     inkscape:window-height="1773"
+     inkscape:window-x="0"
+     inkscape:window-y="27"
+     inkscape:window-maximized="1" />
+  <metadata
+     id="metadata8288">
+    <rdf:RDF>
+      <cc:Work
+         rdf:about="">
+        <dc:format>image/svg+xml</dc:format>
+        <dc:type
+           rdf:resource="http://purl.org/dc/dcmitype/StillImage" />
+        <dc:title></dc:title>
+      </cc:Work>
+    </rdf:RDF>
+  </metadata>
+  <g
+     inkscape:label="Layer 1"
+     inkscape:groupmode="layer"
+     id="layer1"
+     transform="translate(-109.71429,-419.21935)">
+    <g
+       id="icon-cover-play_2_-3"
+       transform="translate(109.71429,419.21935)">
+      <rect
+         height="32"
+         width="32"
+         style="opacity:0;fill:#ffffff"
+         id="icon-cover-play_3_-6"
+         x="0"
+         y="0" />
+      <path
+         id="path8234"
+         d="m 25.342,17.284 c 1.034,-0.706 1.034,-1.861 0,-2.568 L 9.095,3.617 C 8.061,2.911 7.215,3.357 7.215,4.609 l 0,22.781 c 0,1.252 0.846,1.699 1.88,0.993 L 25.342,17.284 Z"
+         style="fill:#ffffff"
+         inkscape:connector-curvature="0" />
+    </g>
+  </g>
+</svg>
diff --git a/qml/cover/ClockView.qml b/qml/cover/ClockView.qml
new file mode 100644 (file)
index 0000000..88652b2
--- /dev/null
@@ -0,0 +1,99 @@
+import QtQuick 2.0
+import Sailfish.Silica 1.0
+
+Item {
+    id: root
+
+    property QtObject timerClock
+
+    // Size of the clock border decoration as a fraction of the radius
+    property real _faceRadiusBase: 84
+    property real _faceHourLength: 14.736 / _faceRadiusBase
+
+    Image {
+        id: clockFace
+
+        anchors.fill: parent
+        fillMode: Image.PreserveAspectFit
+        source: "image://theme/graphic-clock-face-3"
+    }
+
+    ShaderEffect {
+        id: timerVisualization
+
+        anchors.fill: clockFace
+
+        visible: stopwatch.update
+
+        onVisibleChanged: {
+            _updateTimerVisualization()
+        }
+
+        property int _seconds: stopwatch.seconds
+        on_SecondsChanged: _updateTimerVisualization()
+
+        property real outerRadius: 1
+        property real innerRadius: 1 - _faceHourLength
+        property real endAngle
+        property real loopRadius: outerRadius / 10
+        property real count
+
+        property color highlightColor: Theme.rgba(Theme.highlightColor, 0.75)
+
+        function _updateTimerVisualization() {
+            if (stopwatch.fast) {
+                count = _seconds
+            }
+            else {
+                count = Math.floor(_seconds / 60)
+            }
+
+            endAngle = 2 * Math.PI * count / 60
+            innerRadius = outerRadius - loopRadius * (parseInt(count / 60) + 1)
+        }
+
+        vertexShader: "
+            uniform highp mat4 qt_Matrix;
+            attribute highp vec4 qt_Vertex;
+            attribute highp vec2 qt_MultiTexCoord0;
+            varying highp vec2 coord;
+            void main() {
+                coord = qt_MultiTexCoord0;
+                gl_Position = qt_Matrix * qt_Vertex;
+            }"
+        fragmentShader: "
+            varying highp vec2 coord;
+            uniform lowp float qt_Opacity;
+            uniform lowp float outerRadius;
+            uniform lowp float innerRadius;
+            uniform lowp float endAngle;
+            uniform lowp float loopRadius;
+            uniform lowp vec4 highlightColor;
+            uniform lowp float count;
+
+            lowp float PI = 3.14159265358979323846264;
+
+            void main() {
+                highp vec2 vector = 2.0*(coord - vec2(0.5, 0.5));
+                lowp float radius = length(vector);
+                lowp float angle = atan(vector.y, vector.x) + PI/2.0;
+                angle += angle < 0.0 ? 2.0*PI : 0.0;
+
+                lowp float minRadius = outerRadius - ((count / 60.0) + 1.0) * loopRadius + (loopRadius * angle / (2.0 * PI));
+                lowp float maxRadius = outerRadius - ((mod(count, 60.0) / 60.0) + 1.0) * loopRadius + (loopRadius * angle / (2.0 * PI));
+
+                if (angle < 2.0 * PI * mod(count, 60.0) / 60.0) {
+                    maxRadius += loopRadius;
+                }
+
+                if (radius >= minRadius && radius < maxRadius) {
+                    if (mod(radius - minRadius, loopRadius) > 0.2 * loopRadius) {
+                        gl_FragColor = highlightColor;
+                        return;
+                    }
+                }
+                gl_FragColor = vec4(0.0, 0.0, 0.0, 0.0);
+                return;
+            }"
+    }
+}
index fc562d5..a4a911b 100644 (file)
 import QtQuick 2.0
 import Sailfish.Silica 1.0
+import harbour.pedalo.settings 1.0
 
 CoverBackground {
-    Label {
-        id: label
-        anchors.centerIn: parent
-        text: qsTr("My Cover")
+    id: root
+    property bool update: status === Cover.Active && currentStatus.cycling
+
+    onUpdateChanged: {
+        stopwatch.updateseconds()
+        stopwatchtrigger.restart()
+    }
+
+    Item {
+        id: stopwatch
+        property int seconds: 0
+        property int fast: seconds < 60 * 10
+        property bool update: root.update
+
+        Timer {
+            id: stopwatchtrigger
+            interval: stopwatch.fast ? 200 : 5000
+            running: root.update
+            repeat: true
+            triggeredOnStart: true
+            onTriggered: {
+                stopwatch.updateseconds()
+            }
+        }
+
+        function updateseconds() {
+            stopwatch.seconds = (new Date().getTime() - currentStatus.startTime) / 1000
+        }
+    }
+
+    Item {
+        id: contents
+
+        width: Theme.coverSizeLarge.width
+        height: Theme.coverSizeLarge.height
+        property real xScale: root.width / contents.width
+
+        transform: Scale {
+            xScale: contents.xScale
+            yScale: root.height / contents.height
+        }
+
+    ClockView {
+        id: clockView
+
+        anchors {
+            top: parent.top
+            left: parent.left
+            right: parent.right
+            margins: Theme.paddingLarge
+        }
+        height: width
+    }
+
+    LargeItem {
+        id: stopwatchView
+
+        anchors {
+            top: clockView.bottom
+            topMargin: Theme.paddingMedium
+            leftMargin: Theme.paddingLarge
+            rightMargin: Theme.paddingLarge
+        }
+
+        textVisible: root.update
+
+        title: qsTr("Pedalo")
+
+        text: {
+            if (stopwatch.seconds < 60) {
+                return qsTr("%n seconds", "", stopwatch.seconds)
+            } else if (stopwatch.seconds < 60*60) {
+                return qsTr("%1 minutes").arg(Math.floor(stopwatch.seconds/60))
+            } else {
+                var minutes = Math.floor(stopwatch.seconds/60)
+                var hours = Math.floor(minutes/60)
+                minutes = minutes % 60
+                return qsTrId("%1h %2min").arg(hours).arg(minutes)
+            }
+        }
+    }
     }
 
     CoverActionList {
         id: coverAction
 
         CoverAction {
-            iconSource: "image://theme/icon-cover-next"
-        }
+            iconSource: currentStatus.cycling ? Settings.getImageUrl("icon-cover-journey-finish") : Settings.getImageUrl("icon-cover-journey-start")
+            onTriggered: {
+                if (currentStatus.cycling) {
+                    app.activate()
+                    app.showMainPage(PageStackAction.Immediate)
 
-        CoverAction {
-            iconSource: "image://theme/icon-cover-pause"
+                    var dialog = pageStack.push(Qt.resolvedUrl("../pages/JourneyEdit.qml"), {title: "Finish journey", start: journeymodel.epochToDateTime(currentStatus.startTime), duration: currentStatus.getDuration()})
+
+                    dialog.accepted.connect(function() {
+                        currentStatus.cycling = false
+                    })
+                }
+                else {
+                    currentStatus.startJourney()
+                }
+            }
         }
     }
 }
diff --git a/qml/cover/LargeItem.qml b/qml/cover/LargeItem.qml
new file mode 100644 (file)
index 0000000..9d8d887
--- /dev/null
@@ -0,0 +1,36 @@
+import QtQuick 2.0
+import Sailfish.Silica 1.0
+
+Column {
+    property alias title: titleLabel.text
+    property alias titleVisible: titleLabel.visible
+    property alias text: subLabel.text
+    property alias textVisible: subLabel.visible
+
+    anchors {
+        left: parent ? parent.left : undefined
+        right: parent ? parent.right : undefined
+    }
+
+    Label {
+        id: titleLabel
+
+        width: Math.min(parent.width, implicitWidth)
+        x: (parent.width - width) / 2
+
+        color: Theme.primaryColor
+        font.pixelSize: Theme.fontSizeLarge
+        truncationMode: TruncationMode.Fade
+    }
+
+    Label {
+        id: subLabel
+
+        width: Math.min(parent.width, implicitWidth)
+        x: (parent.width - width) / 2
+
+        color: Theme.highlightColor
+        font.pixelSize: Theme.fontSizeExtraSmall
+        truncationMode: TruncationMode.Fade
+    }
+}
index 22ccbbc..3194b9e 100644 (file)
@@ -5,7 +5,19 @@ import harbour.pedalo.settings 1.0
 
 ApplicationWindow
 {
+    id: app
     initialPage: Component { MainPage { } }
     cover: Qt.resolvedUrl("cover/CoverPage.qml")
     allowedOrientations: defaultAllowedOrientations
+
+    function showMainPage(operationType) {
+        var first = pageStack.currentPage
+        var temp = pageStack.previousPage(pageStack.currentPage)
+        while (temp) {
+            first = temp
+            temp = pageStack.previousPage(temp)
+        }
+
+        pageStack.pop(first, operationType)
+    }
 }
index 18ba179..801b26d 100644 (file)
@@ -2,6 +2,13 @@
 <!DOCTYPE TS>
 <TS version="2.1">
 <context>
+    <name></name>
+    <message id="%1h %2min">
+        <source></source>
+        <translation type="unfinished"></translation>
+    </message>
+</context>
+<context>
     <name>About</name>
     <message>
         <source>About Pedalo</source>
 <context>
     <name>CoverPage</name>
     <message>
-        <source>My Cover</source>
-        <translation>Mein Cover</translation>
+        <source>Pedalo</source>
+        <translation type="unfinished"></translation>
+    </message>
+    <message numerus="yes">
+        <source>%n seconds</source>
+        <translation type="unfinished">
+            <numerusform></numerusform>
+        </translation>
+    </message>
+    <message>
+        <source>%1 minutes</source>
+        <translation type="unfinished"></translation>
     </message>
 </context>
 <context>
index 27f1377..801b26d 100644 (file)
@@ -2,6 +2,13 @@
 <!DOCTYPE TS>
 <TS version="2.1">
 <context>
+    <name></name>
+    <message id="%1h %2min">
+        <source></source>
+        <translation type="unfinished"></translation>
+    </message>
+</context>
+<context>
     <name>About</name>
     <message>
         <source>About Pedalo</source>
 <context>
     <name>CoverPage</name>
     <message>
-        <source>My Cover</source>
+        <source>Pedalo</source>
+        <translation type="unfinished"></translation>
+    </message>
+    <message numerus="yes">
+        <source>%n seconds</source>
+        <translation type="unfinished">
+            <numerusform></numerusform>
+        </translation>
+    </message>
+    <message>
+        <source>%1 minutes</source>
         <translation type="unfinished"></translation>
     </message>
 </context>