Merge lp:~nik90/ubuntu-clock-app/world-clock-part1 into lp:ubuntu-clock-app/saucy

Proposed by Nekhelesh Ramananthan
Status: Merged
Approved by: Nekhelesh Ramananthan
Approved revision: 108
Merged at revision: 103
Proposed branch: lp:~nik90/ubuntu-clock-app/world-clock-part1
Merge into: lp:ubuntu-clock-app/saucy
Prerequisite: lp:~nik90/ubuntu-clock-app/initial-visual-design
Diff against target: 607 lines (+506/-59)
4 files modified
clock/ClockPage.qml (+220/-58)
clock/EasterEgg.qml (+1/-1)
clock/WorldClock.qml (+107/-0)
clock/WorldClockModel.qml (+178/-0)
To merge this branch: bzr merge lp:~nik90/ubuntu-clock-app/world-clock-part1
Reviewer Review Type Date Requested Status
Ubuntu Phone Apps Jenkins Bot continuous-integration Approve
Nekhelesh Ramananthan Pending
Review via email: mp+168537@code.launchpad.net

Commit message

Adds the first phase of the world clock's feature. It currently implements the following,
- Necessary UI to show the world clocks
- Search world clocks online using geoname.org API
- World Time is updated every minute locally using existing timer
- Add world clock local storage. Stores world clocks selected by the user
- Clicking on world clock sets it as the current location.
- Add Clock toolbar action

Description of the change

This MP achieves the following,
- Necessary UI to show the world clocks
- Search world clocks online using geoname.org API
- World Time is updated every minute locally using existing timer
- Add world clock local storage. Stores world clocks selected by the user
- Clicking on world clock sets it as the current location.

To post a comment you must log in.
Revision history for this message
Ubuntu Phone Apps Jenkins Bot (ubuntu-phone-apps-jenkins-bot) wrote :
review: Approve (continuous-integration)

Preview Diff

[H/L] Next/Prev Comment, [J/K] Next/Prev File, [N/P] Next/Prev Hunk
=== modified file 'clock/ClockPage.qml'
--- clock/ClockPage.qml 2013-06-09 10:23:09 +0000
+++ clock/ClockPage.qml 2013-06-14 19:10:32 +0000
@@ -20,75 +20,237 @@
2020
21import QtQuick 2.021import QtQuick 2.0
22import Ubuntu.Components 0.122import Ubuntu.Components 0.1
23import Ubuntu.Components.ListItems 0.1 as ListItem
24import QtQuick.XmlListModel 2.0
23import "../common/ClockUtils.js" as Utils25import "../common/ClockUtils.js" as Utils
24import "../common/Constants.js" as Constants26import "../common/Constants.js" as Constants
2527import "../common"
26Page { 28
29Page {
30 id: clockPage;
31
27 // Property to hold the formatted time string to show on the screen32 // Property to hold the formatted time string to show on the screen
28 property string currentTimeFormatted33 property string currentTimeFormatted
34 property real currentTimeStamp
35 property real diff: new Date().getTimezoneOffset()
2936
30 Component.onCompleted: {37 Component.onCompleted: {
31 Utils.log("ClockPage loaded");38 Utils.log("ClockPage loaded");
32 currentTimeFormatted = Qt.formatTime(new Date());39 updateTime();
33 }40 }
3441
35 function onTimerUpdate(now) {42 function onTimerUpdate(now) {
36 currentTimeFormatted = Qt.formatTime(now);43 currentTimeFormatted = Qt.formatTime(new Date(currentTimeStamp + (diff * 60000)), "hh:mm")
44 now.setMinutes(now.getMinutes() + diff)
37 clockFace.timeChanged(now)45 clockFace.timeChanged(now)
38 }46 if (now.getUTCSeconds() == 0) {
3947 updateTime();
40 // Label to show the current time48 }
41 Label {49 }
42 id: currentTimeLabel50
43 objectName: "currentTimeLabel"51 function updateTime() {
4452 var now = new Date();
45 z: clockFace.z + 1;53 currentTimeStamp = now.getTime();
46 width: parent.width; height: parent.height / 454 }
47 anchors.centerIn: clockFace55
48 horizontalAlignment: Text.AlignHCenter56 function getTimeDifference(data) {
49 verticalAlignment: Text.AlignVCenter57 var worldTime = data.split(' ')[1].split(':'),
50 fontSize: "x-large"58 hoursWT = parseInt(worldTime[0]),
51 color: Constants.brightWhite; 59 minutesWT = parseInt(worldTime[1]),
5260 now = new Date();
53 text: currentTimeFormatted61 return ((hoursWT - now.getHours()) * 60 + (minutesWT - now.getMinutes()));
54 }62 }
5563
56 // Qml Element to draw the analogue clock face along with its hour, minute and second hands.64 WorldClockModel {
57 AnalogClockFace {65 id: worldModel;
58 id: clockFace66
5967 Component.onCompleted: {
60 anchors.centerIn: parent68 _loadDB();
61 Component.onCompleted: easterEggCircle.reloadSunRiseModel()69 diff = worldModel.timeDiff;
6270 currentLocation.text = worldModel.city;
63 onClicked: { 71 currentTimeFormatted = Qt.formatTime(new Date(currentTimeStamp + (diff * 60000)), "hh:mm")
64 if (easterEggCircle.isReady == 1) {72 }
65 clockFace.state == "" ? clockFace.state = "easteregg" : clockFace.state = "";73 }
66 } else {74
67 Utils.log("Sunrise/Sunset times not yet loaded.")75 AnimationContainer {
68 }76 id: clockAnimationContainer
69 }77
7078 initYPos: units.gu(0);
71 EasterEgg {79 finalYPos: -listWorldClocks.height;
72 id: easterEggCircle80
73 anchors.centerIn: parent81 anchors.fill: parent
82
83 // Label to show the current time
84 Rectangle {
85 id: labelContainer;
86
87 z: clockFace.z + 1;
74 width: clockFace.innerDimension; height: width;88 width: clockFace.innerDimension; height: width;
75 radius: width / 2;89 anchors.centerIn: clockFace;
76 }90 color: "transparent"
7791
78 states: [92 Label {
79 State {93 id: currentTimeLabel
80 name: "easteregg"94 objectName: "currentTimeLabel"
81 PropertyChanges { target: easterEggCircle; opacity: 1; }95
82 PropertyChanges { target: currentTimeLabel; opacity: 0; }96 anchors.centerIn: parent
83 // TODO: Do some caching of data here. Store data locally to reduce API calls.97 horizontalAlignment: Text.AlignHCenter
84 PropertyChanges { target: easterEggCircle; sunriseLabel: easterEggCircle.getSunTime(easterEggCircle.model.get(0).sunrise); }98 verticalAlignment: Text.AlignVCenter
85 PropertyChanges { target: easterEggCircle; sunsetLabel: easterEggCircle.getSunTime(easterEggCircle.model.get(0).sunset); }99 fontSize: "x-large"
86 },100 color: Constants.brightWhite;
87 State {101
88 name: ""102 text: currentTimeFormatted
89 PropertyChanges { target: currentTimeLabel; opacity: 1; }103 }
90 PropertyChanges { target: easterEggCircle; opacity: 0; }104 }
91 }105
92 ]106 // Qml Element to draw the analogue clock face along with its hour, minute and second hands.
107 AnalogClockFace {
108 id: clockFace
109
110 anchors { top: parent.top; topMargin: units.gu(17); horizontalCenter: parent.horizontalCenter }
111 Component.onCompleted: easterEggCircle.reloadSunRiseModel()
112
113 onClicked: {
114 if (easterEggCircle.isReady == XmlListModel.Ready) {
115 clockFace.state == "" ? clockFace.state = "easteregg" : clockFace.state = "";
116 } else {
117 Utils.log("Sunrise/Sunset times not yet loaded.")
118 }
119 }
120
121 EasterEgg {
122 id: easterEggCircle
123 anchors.centerIn: parent
124 width: clockFace.innerDimension; height: width;
125 radius: width / 2;
126 }
127
128 states: [
129 State {
130 name: "easteregg"
131 PropertyChanges { target: easterEggCircle; opacity: 1; }
132 PropertyChanges { target: currentTimeLabel; opacity: 0; }
133 // TODO: Do some caching of data here. Store data locally to reduce API calls.
134 PropertyChanges { target: easterEggCircle; sunriseLabel: easterEggCircle.getSunTime(easterEggCircle.model.get(0).sunrise); }
135 PropertyChanges { target: easterEggCircle; sunsetLabel: easterEggCircle.getSunTime(easterEggCircle.model.get(0).sunset); }
136 },
137 State {
138 name: ""
139 PropertyChanges { target: currentTimeLabel; opacity: 1; }
140 PropertyChanges { target: easterEggCircle; opacity: 0; }
141 }
142 ]
143 }
144
145 Column {
146 id: savedWorldClock
147
148 height: childrenRect.height;
149 anchors { left:parent.left; right:parent.right; top: clockFace.bottom; topMargin: units.gu(12)}
150
151 ListItem.ThinDivider {}
152
153 ListItem.Header { text: i18n.tr("Current Location") }
154
155 ListItem.SingleValue {
156 id: currentLocation
157 text: "UTC"
158 value: Qt.formatTime(new Date(currentTimeStamp + (diff * 60000)), "hh:mm A");
159 }
160
161 ListItem.Header { text: i18n.tr("World") }
162
163 ListView {
164 id: listWorldClocks
165
166 anchors { left: parent.left; right: parent.right }
167 height: units.gu(30)
168 model: worldModel
169 currentIndex: -1
170
171 delegate: ListItem.Standard {
172 text: cityName;
173
174 Label {
175 fontSize: "medium"
176 text: Qt.formatTime(new Date(currentTimeStamp + (rawOffSet * 60000)), "hh:mm A");
177 anchors { verticalCenter: parent.verticalCenter; right: parent.right; rightMargin: units.gu(2) }
178 }
179
180 selected: listWorldClocks.currentIndex == index
181
182 onClicked: {
183 currentLocation.text = cityName;
184 diff = rawOffSet;
185 worldModel.appendCurrentLocation(cityName, diff);
186 }
187 }
188 }
189 }
190 }
191
192 WorldClock {
193 id: worldClockList;
194
195 anchors { left: parent.left; right: parent.right; top: parent.top; topMargin: units.gu(2) }
196
197 // Xml model to retrieve timezone of searched city
198 XmlListModel {
199 id: cityDetailsModel;
200
201 source: worldClockList.getCityTimezoneUrl(worldClockList.lat, worldClockList.lng)
202 query: "/geonames/timezone"
203
204 onStatusChanged: {
205 if(status == XmlListModel.Ready && worldClockList.city != "null") {
206 worldModel.appendPreset(worldClockList.city, getTimeDifference(cityDetailsModel.get(0).time));
207 }
208 }
209
210 XmlRole { name: "time"; query: "time/string()" }
211 }
212
213 onClicked: {
214 lat = searchModel.get(index).lat;
215 lng = searchModel.get(index).lng;
216 city = searchModel.get(index).city;
217 cityDetailsModel.reload()
218 clockPage.state = "NORMAL"
219 }
220 }
221
222 states: [
223 State {
224 name: "ADDCITY"
225 PropertyChanges { target: worldClockList; visible: true }
226 PropertyChanges { target: clockAnimationContainer; visible: false }
227 PropertyChanges { target: addCity; visible: false }
228 },
229
230 State {
231 name: "NORMAL"
232 PropertyChanges { target: worldClockList; visible: false }
233 PropertyChanges { target: clockAnimationContainer; visible: true }
234 PropertyChanges { target: addCity; visible: true }
235 }
236 ]
237
238 tools: ToolbarActions {
239 id: toolbarClock
240
241 Action {
242 id: addCity
243
244 iconSource: Qt.resolvedUrl("../images/add_icon.png")
245 text: i18n.tr("Add City")
246 visible: true;
247
248 onTriggered: clockPage.state = "ADDCITY";
249 }
250
251 back {
252 visible: true;
253 onTriggered: clockPage.state = "NORMAL";
254 }
93 }255 }
94}256}
95257
=== modified file 'clock/EasterEgg.qml'
--- clock/EasterEgg.qml 2013-06-09 10:23:09 +0000
+++ clock/EasterEgg.qml 2013-06-14 19:10:32 +0000
@@ -52,7 +52,7 @@
52 function getUrlString (latitude, longitude) {52 function getUrlString (latitude, longitude) {
53 var base_url,url;53 var base_url,url;
54 base_url = "http://api.openweathermap.org/data/2.5/weather?";54 base_url = "http://api.openweathermap.org/data/2.5/weather?";
55 url = base_url + "lat=" + latitude + "&lon=" + longitude + "&mode=xml";55 url = base_url + "lat=" + latitude + "&lon=" + longitude + "&mode=xml";
56 return url;56 return url;
57 }57 }
5858
5959
=== added file 'clock/WorldClock.qml'
--- clock/WorldClock.qml 1970-01-01 00:00:00 +0000
+++ clock/WorldClock.qml 2013-06-14 19:10:32 +0000
@@ -0,0 +1,107 @@
1import QtQuick 2.0
2import Ubuntu.Components 0.1
3import Ubuntu.Components.ListItems 0.1 as ListItem
4import QtQuick.XmlListModel 2.0
5import "../common/ClockUtils.js" as Utils
6import "../common/Constants.js" as Constants
7
8Column {
9 id: worldClocks
10
11 // Properties to store the user searched city details
12 property string city: "null";
13 property real lng: 0;
14 property real lat: 0;
15 property real rawOffSet: 0;
16
17 // Property to store the city searched by the user
18 property string search_string: "london";
19
20 // Properties to store the information related to api.geonames.org
21 readonly property string username: "krnekhelesh";
22 readonly property string search_base_url: "http://api.geonames.org/search?q=";
23 readonly property string timezone_base_url: "http://api.geonames.org/timezone?lat=";
24 readonly property int no_of_results: 5;
25
26 property alias searchModel: searchCityModel;
27 property alias index: worldList.currentIndex;
28
29 signal clicked()
30
31 function searchCityUrl (city) {
32 return (search_base_url + search_string + "&maxRows="+ no_of_results + "&username=" + username);
33 }
34
35 function getCityTimezoneUrl (lat, lng) {
36 return (timezone_base_url + lat + "&lng=" + lng + "&username=" + username);
37 }
38
39 height: childrenRect.height;
40 visible: false;
41
42 // Xml model to search city online and retrieve latitude and longitude of searched city
43 XmlListModel {
44 id: searchCityModel;
45
46 source: searchCityUrl(city)
47 query: "/geonames/geoname"
48
49 XmlRole { name: "city"; query: "toponymName/string()"; isKey: true }
50 XmlRole { name: "lat"; query: "lat/string()"; isKey: true }
51 XmlRole { name: "lng"; query: "lng/string()"; isKey: true }
52 }
53
54 Row {
55 id: searchBox;
56
57 spacing: units.gu(1)
58 anchors.horizontalCenter: parent.horizontalCenter;
59 height: childrenRect.height;
60
61 TextField {
62 id: searchLabel
63
64 hasClearButton: true
65 placeholderText: i18n.tr("Search");
66 }
67
68 Button {
69 id: searchButton;
70
71 width: units.gu(12); height: searchLabel.height;
72 color: Constants.green;
73 text: i18n.tr("Search")
74
75 onClicked: {
76 if (searchLabel.text != "") {
77 search_string = searchLabel.text;
78 searchCityModel.reload();
79 }
80 }
81 }
82
83 ActivityIndicator {
84 running: searchCityModel.status === XmlListModel.Loading
85 }
86 }
87
88 ListView {
89 id: worldList
90
91 anchors { left: parent.left; right: parent.right }
92 height: units.gu(35)
93 model: searchCityModel
94 currentIndex: -1
95
96 delegate: ListItem.Standard {
97 id: delegateTest;
98 text: searchCityModel.status == XmlListModel.Ready ? searchCityModel.get(index).city : i18n.tr("Loading...");
99 selected: worldList.currentIndex == index;
100
101 onClicked: {
102 worldList.currentIndex = index;
103 worldClocks.clicked()
104 }
105 }
106 }
107}
0108
=== added file 'clock/WorldClockModel.qml'
--- clock/WorldClockModel.qml 1970-01-01 00:00:00 +0000
+++ clock/WorldClockModel.qml 2013-06-14 19:10:32 +0000
@@ -0,0 +1,178 @@
1/*
2 * Copyright (C) 2013 Canonical Ltd
3 *
4 * This program is free software: you can redistribute it and/or modify
5 * it under the terms of the GNU General Public License version 3 as
6 * published by the Free Software Foundation.
7 *
8 * This program is distributed in the hope that it will be useful,
9 * but WITHOUT ANY WARRANTY; without even the implied warranty of
10 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
11 * GNU General Public License for more details.
12 *
13 * You should have received a copy of the GNU General Public License
14 * along with this program. If not, see <http://www.gnu.org/licenses/>.
15 *
16 * Authored by: Nekhelesh Ramananthan <krnekhelesh@gmail.com>
17 */
18
19import QtQuick 2.0
20import QtQuick.LocalStorage 2.0
21import "../common/ClockUtils.js" as Utils
22
23ListModel {
24 id: model
25
26 readonly property string dbName: "ubuntu-clock-app"
27 readonly property string dbVersion: "0.1"
28 readonly property string dbDescription: "World Clock"
29
30 property real timeDiff: new Date().getTimezoneOffset()
31 property string city: "UTC";
32
33 property var _db: null
34
35 function appendCurrentLocation(cityname, rawOffSet)
36 {
37 try {
38 _db.transaction(function(tx){
39 var currentValue;
40 var res = tx.executeSql('SELECT * FROM CurrentClock');
41 if (res.rows.length > 0) {
42 currentValue = res.rows.item(0).rawOffSet;
43 }
44
45 if (currentValue !== rawOffSet) {
46 // Update existing value or insert if none
47 Utils.log("Outer loop");
48 if (currentValue !== undefined) {
49 res = tx.executeSql('UPDATE CurrentClock SET rawOffSet = ?, cityName = ?', [rawOffSet, cityname]);
50 Utils.log("Existing record found. Hence updating it!");
51 } else {
52 res = tx.executeSql('INSERT INTO CurrentClock VALUES(?, ?)', [cityname, rawOffSet]);
53 Utils.log("No City found. Creating a new entry.");
54 }
55 }
56 });
57 } catch (err) {
58 Utils.error("Error setting current location '" + "': " + err);
59 return false;
60 }
61 }
62
63 function appendPreset(cityName, rawOffSet)
64 {
65 if (_appendDB(cityName, rawOffSet)) {
66 model.append({"cityName" : cityName, "rawOffSet" : rawOffSet})
67 }
68 }
69
70 function removePreset(index)
71 {
72 var cityName = get(index).cityName
73 if (_removeDB(cityName)) {
74 model.remove(index);
75 }
76 }
77
78 function _createDB() {
79 if (_db == null) return false;
80 try {
81 _db.transaction(function(tx){
82 tx.executeSql('CREATE TABLE IF NOT EXISTS WorldClock(cityName TEXT, rawOffSet REAL)');
83 });
84 } catch (err) {
85 Utils.error("Error creating WorldClock table in db:" + err);
86 return false;
87 }
88 try {
89 _db.transaction(function(tx){
90 tx.executeSql('CREATE TABLE IF NOT EXISTS CurrentClock(cityName TEXT, rawOffSet REAL)');
91 });
92 } catch (err) {
93 Utils.error("Error creating CurrentClock table in db:" + err);
94 return false;
95 }
96 return true;
97 }
98
99 function _loadDB() {
100 _db = LocalStorage.openDatabaseSync(model.dbName, model.dbVersion, model.dbDescription, 1000);
101 if (_db == null) return false;
102 if (!_createDB()) {
103 _db = null;
104 return false;
105 }
106 try {
107 _db.readTransaction(function(tx){
108 var res = tx.executeSql('SELECT * FROM WorldClock');
109 if (res.rows.length > 0) {
110 for (var i = 0; i < res.rows.length; i++) {
111 model.append({"cityName" : res.rows.item(i).cityName,
112 "rawOffSet" : res.rows.item(i).rawOffSet});
113 }
114 }
115 });
116 } catch (err) {
117 Utils.error("Error opening database: " + err);
118 _db = null
119 return false;
120 }
121 try {
122 _db.readTransaction(function(tx){
123 var currentstate = tx.executeSql('SELECT * FROM CurrentClock ORDER BY cityName');
124 if (currentstate.rows.length > 0) {
125 city = currentstate.rows.item(0).cityName;
126 timeDiff = currentstate.rows.item(0).rawOffSet
127 Utils.log("Inside reading")
128 }
129 });
130 } catch (err) {
131 Utils.error("Error opening database: " + err);
132 _db = null
133 return false;
134 }
135 return true;
136 }
137
138 function _appendDB(cityName, rawOffSet) {
139 if (_db == null) return false;
140
141 try {
142 _db.transaction(function(tx){
143 var currentValue;
144 var res = tx.executeSql('SELECT rawOffSet FROM WorldClock WHERE cityName=?', cityName);
145 if (res.rows.length > 0) {
146 currentValue = res.rows.item(0).rawOffSet;
147 }
148
149 if (currentValue !== rawOffSet) {
150 // Update existing value or insert if none
151 if (currentValue !== undefined) {
152 res = tx.executeSql('UPDATE WorldClock SET rawOffSet = ? WHERE cityName = ?', [rawOffSet, cityName]);
153 } else {
154 res = tx.executeSql('INSERT INTO WorldClock VALUES(?, ?)', [cityName, rawOffSet]);
155 }
156 }
157 });
158 } catch (err) {
159 Utils.error("Error setting labeltxt '"+ label + "': " + err);
160 return false;
161 }
162 return true;
163 }
164
165 function _removeDB(cityName) {
166 if (_db == null) return false;
167
168 try {
169 _db.transaction(function(tx){
170 tx.executeSql('DELETE FROM WorldClock WHERE cityName = ?', [cityName]);
171 });
172 } catch (err) {
173 Utils.error("Error delete WorldClock'" + cityName + "': " + err);
174 return false;
175 }
176 return true;
177 }
178}

Subscribers

People subscribed via source and target branches