← Back to team overview

ubuntu-touch-coreapps-reviewers team mailing list archive

[Merge] lp:~nik90/ubuntu-weather-app/1-improve-add-location-page into lp:ubuntu-weather-app/reboot

 

Nekhelesh Ramananthan has proposed merging lp:~nik90/ubuntu-weather-app/1-improve-add-location-page into lp:ubuntu-weather-app/reboot with lp:~nik90/ubuntu-weather-app/0-fix-broken-settings-links as a prerequisite.

Requested reviews:
  Ubuntu Weather Developers (ubuntu-weather-dev)

For more details, see:
https://code.launchpad.net/~nik90/ubuntu-weather-app/1-improve-add-location-page/+merge/252115

This MP brings the Add Location Page more in line with Clock app's own add city page to maintain consistency. Also there was no design specified for the add location page in weather app's design spec.

- Added fast scroll to add location page
- Renamed AddPage.qml to AddLocationPage.qml to be more specific
- Added section headers to listview
- Removed focus on search textfield immediately after opening the add location page. The reasoning here being that we show a list of cities that the user can choose from. Only if doesnt find his city there should he resort to searching online.
- Moved the search textfield to a component to allow for dynamic loading
-- 
Your team Ubuntu Weather Developers is requested to review the proposed merge of lp:~nik90/ubuntu-weather-app/1-improve-add-location-page into lp:ubuntu-weather-app/reboot.
=== added file 'app/components/FastScroll.js'
--- app/components/FastScroll.js	1970-01-01 00:00:00 +0000
+++ app/components/FastScroll.js	2015-03-06 14:21:02 +0000
@@ -0,0 +1,130 @@
+/****************************************************************************
+**
+** Copyright (C) 2011 Nokia Corporation and/or its subsidiary(-ies).
+** Copyright (C) 2014 Canonical Ltda
+** All rights reserved.
+** Contact: Nokia Corporation (qt-info@xxxxxxxxx)
+**
+** This file is part of the Qt Components project.
+**
+** $QT_BEGIN_LICENSE:BSD$
+** You may use this file under the terms of the BSD license as follows:
+**
+** "Redistribution and use in source and binary forms, with or without
+** modification, are permitted provided that the following conditions are
+** met:
+**   * Redistributions of source code must retain the above copyright
+**     notice, this list of conditions and the following disclaimer.
+**   * 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.
+**   * Neither the name of Nokia Corporation and its Subsidiary(-ies) 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
+** OWNER 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."
+** $QT_END_LICENSE$
+**
+****************************************************************************/
+
+// FastScroll.js - this is just SectionScroller.js with a fix for
+// section.criteria == ViewSection.FirstCharacter
+var sectionData = [];
+var _sections = [];
+
+function initialize(list) {
+    initSectionData(list);
+}
+
+function contains(name) {
+    return (_sections.indexOf(name) > -1)
+}
+
+function initSectionData(list) {
+    if (!list || !list.model) return;
+    sectionData = [];
+    _sections = [];
+    var current = "",
+        prop = list.section.property,
+        sectionText;
+
+    if (list.section.criteria === ViewSection.FullString) {
+        for (var i = 0, count = list.model.count; i < count; i++) {
+            sectionText = list.getSectionText(i)
+            if (sectionText !== current) {
+                current = sectionText;
+                _sections.push(current);
+                sectionData.push({ index: i, header: current });
+            }
+        }
+    } else if (list.section.criteria === ViewSection.FirstCharacter) {
+        for (var i = 0, count = list.model.count; i < count; i++) {
+            sectionText = list.getSectionText(i).substring(0, 1)
+            if (sectionText !== current) {
+                current = sectionText
+                _sections.push(sectionText);
+                sectionData.push({ index: i, header: current });
+            }
+        }
+    }
+}
+
+function getSectionPositionString(name) {
+    var val = _sections.indexOf(name);
+    return val === 0 ? "first" :
+           val === _sections.length - 1 ? "last" : false;
+}
+
+function getAt(pos) {
+    return _sections[pos] ? _sections[pos] : "";
+}
+
+function getRelativeSections(current) {
+    var val = _sections.indexOf(current),
+        sect = [],
+        sl = _sections.length;
+
+    val = val < 1 ? 1 : val >= sl-1 ? sl-2 : val;
+    sect = [getAt(val - 1), getAt(val), getAt(val + 1)];
+
+    return sect;
+}
+
+function getClosestSection(pos, down) {
+    var tmp = (_sections.length) * pos;
+    var val = Math.ceil(tmp) // TODO: better algorithm
+    val = val < 2 ? 1 : val;
+    return _sections[val-1];
+}
+
+function getNextSection(current) {
+    var val = _sections.indexOf(current);
+    return (val > -1 ? _sections[(val < _sections.length - 1 ? val + 1 : val)] : _sections[0]) || "";
+}
+
+function getPreviousSection(current) {
+    var val = _sections.indexOf(current);
+    return (val > -1 ? _sections[(val > 0 ? val - 1 : val)] : _sections[0]) || "";
+}
+
+function getIndexFor(sectionName) {
+    var data = sectionData[_sections.indexOf(sectionName)]
+    if (data) {
+        var val = data.index;
+        return val === 0 || val > 0 ? val : -1;
+    } else {
+        return -1
+    }
+}

=== added file 'app/components/FastScroll.qml'
--- app/components/FastScroll.qml	1970-01-01 00:00:00 +0000
+++ app/components/FastScroll.qml	2015-03-06 14:21:02 +0000
@@ -0,0 +1,321 @@
+/****************************************************************************
+**
+** Copyright (C) 2011 Nokia Corporation and/or its subsidiary(-ies).
+** Copyright (C) 2014 Canonical Ltda
+** All rights reserved.
+** Contact: Nokia Corporation (qt-info@xxxxxxxxx)
+**
+** This file is part of the Qt Components project.
+**
+** $QT_BEGIN_LICENSE:BSD$
+** You may use this file under the terms of the BSD license as follows:
+**
+** "Redistribution and use in source and binary forms, with or without
+** modification, are permitted provided that the following conditions are
+** met:
+**   * Redistributions of source code must retain the above copyright
+**     notice, this list of conditions and the following disclaimer.
+**   * 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.
+**   * Neither the name of Nokia Corporation and its Subsidiary(-ies) 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
+** OWNER 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."
+** $QT_END_LICENSE$
+**
+****************************************************************************/
+
+// FastScroll.qml
+import QtQuick 2.3
+import Ubuntu.Components 1.1
+import "FastScroll.js" as Sections
+
+Item {
+    id: root
+
+    property ListView listView
+    property int pinSize: units.gu(2)
+
+    readonly property var letters: ["A", "B", "C", "D", "E", "F", "G", "H", "I", "J", "K", "L", "M", "N", "O", "P", "Q", "R", "S", "T", "U", "V", "W", "X", "Y", "Z"]
+    readonly property alias fastScrolling: internal.fastScrolling
+    readonly property bool showing: (rail.opacity !== 0.0)
+    readonly property double minimumHeight: rail.height
+
+    width: units.gu(7)
+    height: rail.height
+
+    onListViewChanged: {
+        if (listView && listView.model) {
+            internal.initDirtyObserver();
+        } else if (listView) {
+            listView.modelChanged.connect(function() {
+                if (listView.model) {
+                    internal.initDirtyObserver();
+                }
+            });
+        }
+    }
+
+    Connections {
+        target: listView
+        onCurrentIndexChanged: {
+            if (currentIndex != -1) {
+                rail.opacity = 0.0
+            }
+        }
+    }
+
+    Rectangle {
+        id: magnified
+
+        color: Theme.palette.normal.overlay
+        radius: height * 0.3
+        height: pinSize * 2
+        width: height
+        opacity: internal.fastScrolling && root.enabled ? 1.0 : 0.0
+        x: -cursor.width - units.gu(3)
+        y: {
+            if (internal.currentItem) {
+                var itemCenterY = rail.y + internal.currentItem.y + (internal.currentItem.height / 2)
+                return (itemCenterY - (magnified.height / 2))
+            } else {
+                return 0
+            }
+        }
+
+        Label {
+            anchors.fill: parent
+            horizontalAlignment: Text.AlignHCenter
+            verticalAlignment: Text.AlignVCenter
+            text: internal.desireSection
+            fontSize: "small"
+        }
+
+        Behavior on opacity {
+            UbuntuNumberAnimation {}
+        }
+    }
+
+    Rectangle {
+        id: cursor
+
+        property bool showLabel: false
+        property string currentSectionName: ""
+
+        radius: pinSize * 0.3
+        height: pinSize
+        width: height
+        color: Theme.palette.normal.foreground
+        opacity: rail.opacity
+        x: rail.x
+        y: {
+            if (internal.currentItem) {
+                var itemCenterY = rail.y + internal.currentItem.y + (internal.currentItem.height / 2)
+                return (itemCenterY - (cursor.height / 2))
+            } else {
+                return 0
+            }
+        }
+        Behavior on y {
+            enabled: !internal.fastScrolling
+            UbuntuNumberAnimation { }
+        }
+    }
+
+    Column {
+        id: rail
+
+        property bool isVisible: root.enabled &&
+                                 (listView.flicking || dragArea.pressed) &&
+                                 (listView.currentIndex == -1)
+        anchors {
+            right: parent.right
+            rightMargin: units.gu(2)
+            left: parent.left
+            leftMargin: units.gu(2)
+            top: parent.top
+        }
+        height: childrenRect.height
+        opacity: 0.0
+        onIsVisibleChanged: {
+            if (isVisible) {
+                rail.opacity = 1.0
+                hideTimer.stop()
+            } else if (!root.enabled) {
+                rail.opacity = 0.0
+            } else {
+                hideTimer.restart()
+            }
+        }
+
+        Behavior on opacity {
+            UbuntuNumberAnimation { }
+        }
+
+        Repeater {
+            id: sectionsRepeater
+
+            model: root.letters
+            Label {
+                id: lbl
+
+                anchors.left: parent.left
+                height: pinSize
+                width: pinSize
+                verticalAlignment: Text.AlignVCenter
+                horizontalAlignment: Text.AlignHCenter
+                text: modelData
+                fontSize: "x-small"
+                color: cursor.y === y ? "white" : Theme.palette.selected.backgroundText
+                opacity: !internal.modelDirty && Sections.contains(text) ? 1.0 : 0.5
+            }
+        }
+
+        Timer {
+            id: hideTimer
+
+            running: false
+            interval: 2000
+            onTriggered: rail.opacity = 0.0
+        }
+    }
+
+    MouseArea {
+        id: dragArea
+
+        anchors {
+            left: parent.left
+            right: parent.right
+        }
+        y: rail.y
+        height: rail.height
+        visible: rail.opacity == 1.0
+
+        preventStealing: true
+        onPressed: {
+            internal.adjustContentPosition(mouseY)
+            dragginTimer.start()
+        }
+
+        onReleased: {
+            dragginTimer.stop()
+            internal.desireSection = ""
+            internal.fastScrolling = false
+        }
+
+        onPositionChanged: internal.adjustContentPosition(mouseY)
+
+        Timer {
+            id: dragginTimer
+
+            running: false
+            interval: 150
+            onTriggered: {
+                internal.fastScrolling = true
+            }
+        }
+    }
+
+    Timer {
+        id: dirtyTimer
+        interval: 500
+        running: false
+        onTriggered: {
+            Sections.initSectionData(listView);
+            internal.modelDirty = false;
+        }
+    }
+
+    Timer {
+        id: timerScroll
+
+        running: false
+        interval: 10
+        onTriggered: {
+            if (internal.desireSection != internal.currentSection) {
+                var idx = Sections.getIndexFor(internal.desireSection)
+                if (idx !== -1) {
+                    listView.cancelFlick()
+                    listView.positionViewAtIndex(idx, ListView.Beginning)
+                }
+            }
+        }
+    }
+
+    QtObject {
+        id: internal
+
+        property string currentSection: listView.currentSection
+        property string desireSection: ""
+        property string targetSection: fastScrolling ? desireSection : currentSection
+        property int oldY: 0
+        property bool modelDirty: false
+        property bool down: true
+        property bool fastScrolling: false
+        property var currentItem: null
+
+        onTargetSectionChanged: moveIndicator(targetSection)
+
+        function initDirtyObserver() {
+            Sections.initialize(listView);
+            function dirtyObserver() {
+                if (!internal.modelDirty) {
+                    internal.modelDirty = true;
+                    dirtyTimer.running = true;
+                }
+            }
+
+            if (listView.model.countChanged)
+                listView.model.countChanged.connect(dirtyObserver);
+
+            if (listView.model.itemsChanged)
+                listView.model.itemsChanged.connect(dirtyObserver);
+
+            if (listView.model.itemsInserted)
+                listView.model.itemsInserted.connect(dirtyObserver);
+
+            if (listView.model.itemsMoved)
+                listView.model.itemsMoved.connect(dirtyObserver);
+
+            if (listView.model.itemsRemoved)
+                listView.model.itemsRemoved.connect(dirtyObserver);
+        }
+
+        function adjustContentPosition(mouseY) {
+            var child = rail.childAt(rail.width / 2, mouseY)
+            if (!child || child.text === "") {
+                return
+            }
+            var section = child.text
+            if (internal.desireSection !== section) {
+                internal.desireSection = section
+                moveIndicator(section)
+                if (dragArea.pressed) {
+                    timerScroll.restart()
+                }
+            }
+        }
+
+        function moveIndicator(section)
+        {
+            var index = root.letters.indexOf(section)
+            if (index != -1) {
+                currentItem = sectionsRepeater.itemAt(index)
+            }
+        }
+    }
+}

=== renamed file 'app/ui/AddPage.qml' => 'app/ui/AddLocationPage.qml'
--- app/ui/AddPage.qml	2015-03-03 13:52:31 +0000
+++ app/ui/AddLocationPage.qml	2015-03-06 14:21:02 +0000
@@ -25,32 +25,77 @@
 import "../data/WeatherApi.js" as WeatherApi
 
 Page {
-    id: addPage
-    title: i18n.tr("Add city")
-
-    head.contents: TextField {
-        id: searchField
-        anchors {
-           left: parent ? parent.left : undefined
-           right: parent ? parent.right : undefined
-           rightMargin: units.gu(2)
-        }
-        hasClearButton: true
-        inputMethodHints: Qt.ImhNoPredictiveText
-        placeholderText: i18n.tr("Search city")
-
-        onTextChanged: {
-            if (text.trim() === "") {
-                loadEmpty()
-            } else {
-                loadFromProvider(text)
-            }
-        }
-
-        onVisibleChanged: {
-           if (visible) {
-               forceActiveFocus()
-           }
+    id: addLocationPage
+
+    title: i18n.tr("Select a city")
+    visible: false
+
+    /*
+      Flickable is set to null to stop page header from hiding since the fast
+      scroll component hides top anchor margin is incorrect.
+    */
+    flickable: null
+
+    state: "default"
+    states: [
+        PageHeadState {
+            name: "default"
+            head: addLocationPage.head
+            actions: [
+                Action {
+                    iconName: "search"
+                    text: i18n.tr("City")
+                    onTriggered: {
+                        addLocationPage.state = "search"
+                        searchComponentLoader.sourceComponent = searchComponent
+                        searchComponentLoader.item.forceActiveFocus()
+                    }
+                }
+            ]
+        },
+
+        PageHeadState {
+            name: "search"
+            head: addLocationPage.head
+            backAction: Action {
+                iconName: "back"
+                text: i18n.tr("Back")
+                onTriggered: {
+                    locationList.forceActiveFocus()
+                    searchComponentLoader.item.text = ""
+                    addLocationPage.state = "default"
+                    searchComponentLoader.sourceComponent = undefined
+                }
+            }
+
+            contents: Loader {
+                id: searchComponentLoader
+                anchors {
+                    left: parent ? parent.left : undefined
+                    right: parent ? parent.right : undefined
+                    rightMargin: units.gu(2)
+                }
+            }
+        }
+    ]
+
+    Component {
+        id: searchComponent
+        TextField {
+            id: searchField
+            objectName: "searchField"
+
+            inputMethodHints: Qt.ImhNoPredictiveText
+            placeholderText: i18n.tr("Search city")
+            hasClearButton: true
+
+            onTextChanged: {
+                if (text.trim() === "") {
+                    loadEmpty()
+                } else {
+                    loadFromProvider(text)
+                }
+            }
         }
     }
 
@@ -78,12 +123,12 @@
         clearModelForLoading()
 
         WeatherApi.sendRequest({
-            action: "searchByName",
-            params: {
-                name: search,
-                units: "metric"
-            }
-        }, searchResponseHandler)
+                                   action: "searchByName",
+                                   params: {
+                                       name: search,
+                                       units: "metric"
+                                   }
+                               }, searchResponseHandler)
     }
 
     function searchResponseHandler(msgObject) {
@@ -97,10 +142,30 @@
     }
 
     ListView {
-        id: addLocationView
-        anchors {
-            fill: parent
-        }
+        id: locationList
+
+        clip: true
+        currentIndex: -1
+        anchors.fill: parent
+        anchors.rightMargin: fastScroll.showing ? fastScroll.width - units.gu(1)
+                                                : 0
+
+        function getSectionText(index) {
+            return citiesModel.get(index).name.substring(0,1)
+        }
+
+        onFlickStarted: {
+            forceActiveFocus()
+        }
+
+        section.property: "name"
+        section.criteria: ViewSection.FirstCharacter
+        section.labelPositioning: ViewSection.InlineLabels
+
+        section.delegate: ListItem.Header {
+            text: section
+        }
+
         model: ListModel {
             id: citiesModel
 
@@ -109,7 +174,9 @@
 
             onRowsAboutToBeInserted: loading = false
         }
+
         delegate: ListItem.Empty {
+            showDivider: false
             Column {
                 anchors {
                     left: parent.left
@@ -118,20 +185,19 @@
                     rightMargin: units.gu(2)
                     verticalCenter: parent.verticalCenter
                 }
-                spacing: units.gu(0.5)
 
                 Label {
                     color: UbuntuColors.darkGrey
                     elide: Text.ElideRight
                     fontSize: "medium"
-                    text: model.name
+                    text: name
                 }
 
                 Label {
                     color: UbuntuColors.lightGrey
                     elide: Text.ElideRight
-                    fontSize: "small"
-                    text: model.countryName
+                    fontSize: "xx-small"
+                    text: countryName
                 }
             }
 
@@ -145,6 +211,26 @@
         }
 
         Component.onCompleted: loadEmpty()
+
+        Behavior on anchors.rightMargin {
+            UbuntuNumberAnimation {}
+        }
+    }
+
+    FastScroll {
+        id: fastScroll
+
+        listView: locationList
+
+        enabled: (locationList.contentHeight > (locationList.height * 2)) &&
+                 (locationList.height >= minimumHeight)
+
+        anchors {
+            top: locationList.top
+            topMargin: units.gu(1.5)
+            bottom: locationList.bottom
+            right: parent.right
+        }
     }
 
     ActivityIndicator {

=== modified file 'app/ui/LocationsPage.qml'
--- app/ui/LocationsPage.qml	2015-03-03 18:37:59 +0000
+++ app/ui/LocationsPage.qml	2015-03-06 14:21:02 +0000
@@ -37,7 +37,7 @@
             actions: [
                 Action {
                     iconName: "add"
-                    onTriggered: mainPageStack.push(Qt.resolvedUrl("AddPage.qml"))
+                    onTriggered: mainPageStack.push(Qt.resolvedUrl("AddLocationPage.qml"))
                 }
             ]
             PropertyChanges {

=== modified file 'po/com.ubuntu.weather.pot'
--- po/com.ubuntu.weather.pot	2015-03-06 14:21:02 +0000
+++ po/com.ubuntu.weather.pot	2015-03-06 14:21:02 +0000
@@ -8,7 +8,7 @@
 msgstr ""
 "Project-Id-Version: ubuntu-weather-app\n"
 "Report-Msgid-Bugs-To: \n"
-"POT-Creation-Date: 2015-03-06 14:23+0100\n"
+"POT-Creation-Date: 2015-03-06 15:08+0100\n"
 "PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n"
 "Last-Translator: FULL NAME <EMAIL@ADDRESS>\n"
 "Language-Team: LANGUAGE <LL@xxxxxx>\n"
@@ -37,27 +37,35 @@
 msgid "Cancel selection"
 msgstr ""
 
-#: ../app/ui/AddPage.qml:29
-msgid "Add city"
-msgstr ""
-
-#: ../app/ui/AddPage.qml:40
+#: ../app/ui/AddLocationPage.qml:30
+msgid "Select a city"
+msgstr ""
+
+#: ../app/ui/AddLocationPage.qml:47
+msgid "City"
+msgstr ""
+
+#: ../app/ui/AddLocationPage.qml:62
+msgid "Back"
+msgstr ""
+
+#: ../app/ui/AddLocationPage.qml:89
 msgid "Search city"
 msgstr ""
 
-#: ../app/ui/AddPage.qml:163
+#: ../app/ui/AddLocationPage.qml:249
 msgid "No city found"
 msgstr ""
 
-#: ../app/ui/AddPage.qml:176
+#: ../app/ui/AddLocationPage.qml:262
 msgid "Couldn't load weather data, please try later again!"
 msgstr ""
 
-#: ../app/ui/AddPage.qml:186
+#: ../app/ui/AddLocationPage.qml:272
 msgid "Location already added."
 msgstr ""
 
-#: ../app/ui/AddPage.qml:189
+#: ../app/ui/AddLocationPage.qml:275
 msgid "OK"
 msgstr ""
 
@@ -65,19 +73,19 @@
 msgid "Locations"
 msgstr ""
 
-#: ../app/ui/SettingsPage.qml:25
+#: ../app/ui/SettingsPage.qml:24
 msgid "Settings"
 msgstr ""
 
-#: ../app/ui/SettingsPage.qml:42 ../app/ui/settings/UnitsPage.qml:25
+#: ../app/ui/SettingsPage.qml:41 ../app/ui/settings/UnitsPage.qml:25
 msgid "Units"
 msgstr ""
 
-#: ../app/ui/SettingsPage.qml:49 ../app/ui/settings/DataProviderPage.qml:25
+#: ../app/ui/SettingsPage.qml:48 ../app/ui/settings/DataProviderPage.qml:25
 msgid "Data Provider"
 msgstr ""
 
-#: ../app/ui/SettingsPage.qml:56 ../app/ui/settings/RefreshIntervalPage.qml:25
+#: ../app/ui/SettingsPage.qml:55 ../app/ui/settings/RefreshIntervalPage.qml:25
 msgid "Refresh Interval"
 msgstr ""
 


Follow ups